# 富士○japanの「2人同時に発行申請すると上書き」バグの原因と対処方
<!--
https://www.itmedia.co.jp/news/articles/2305/10/news114.html
-->
---
## 皆さんこんにちは!
:wave:
---
<!--
初めましての方も多いと思いますので自己紹介させて
いただきます。
フリーランスのバックエンドエンジニアをやっております
音川といいます。
-->
My Profile
Otogawa Katsutoshi
freelance backend enginner
interesting
deno, golang, python, and more...
[twitter account](https://twitter.com/k_otogawa)
---
<!-- Setting 舞台 -->
## 突然ですがみなさん!役所のシステムは好きですか!
:sunglasses:
---
## 私はたまにブチギレそうになります😇
---
<!-- 今回私が何しに来たか?というと -->
## 今回何しに来たか?というと
神奈川県川崎市のコンビニの証明書交付サービスで、他人の戸籍謄本が発行されるバグがヤバすぎるのでその内容と対処法を説明しに来ました。
## 要するに
---
## 役所のシステムの闇を暴く!

---
## 前提条件というか縛り
クラウドやvps, オンプレ云々の違いなどインフラベンダー、DBベンダー側の設計で差が出ないようにします
---
## そもそもどういう不具合か?

<!-- 半年ぐらい前の事件なのでおさらいさせていただきます。 -->
---
<!-- 富士通japanaがどういう障害を出したかってのが、 -->
## 会社のサイトに書かれている
<!-- それをちょっと見てみましょう。 -->
https://www.fujitsu.com/jp/group/fjj/about/resources/news/topics/2023/0330.html
---
## これを要約すると
3月27日(月)11:40コンビニで証明書交付申請をされる方が増加し、取引負荷が高まったため、印刷処理における遅延が発生いたしました。
---
この遅延に起因し、システム上設定されていたタイムアウトの上限を超える状態となり強制的な印刷処理の解除が生じ、次の印刷イメージファイルを誤って取得したため、申請された方とは異なる住民の方の証明書が発行されました。
---
### これを見て
ん?って思った方は多いと思います。

---
## そもそも説明がおかしい
1. 高負荷だからタイムアウトしたことが、
2. 印刷時に違うファイルをとってきたことの説明になってない。
---
みなさんの会社のシステムは重くなったら、ユーザーの個人情報をpdfでダウンロードするときに違う人のファイルに入れ替わりますでしょうか?
ならないと思います。
<!--
みなさんの会社のシステムは重くなったら、ユーザーの個人情報をpdfでダウンロードするときに違う人のファイルに入れ替わりますでしょうか?ならないと思います。
おかしいとは思いますが、
-->
---
## そうは言ってもどれぐらいの負荷か?
<!-- おおよそどの程度か? -->
一応、ネットに情報があったので、Google Bardに聞いた情報を合わせて考えてみます。
問題が起きた川崎市以外でもどの程度の負荷なのか?
他の市だと?
と考えてみます。
---
## どれぐらいの人口でコンビニ数がどれくらいか?
一つの基準で100万人都市を考えてみます。
100万人都市は日本で12個しかない。
---
だから、これらの市で負荷的に大丈夫なら、日本の市で負荷で困ることはない。
<!-- だから、こいつらで負荷的に大丈夫なら、日本の市で負荷で困ることはない。 -->
---
## 人口と、コンビニ数
問題が起きた川崎市は人口153万人で、コンビニ数は503件
日本で最も人口が多い横浜市は377万人で、コンビニ数は1189件
<!-- なので、 -->
---
## 最も負荷がある都市で
人数がそのまま負荷になると考えても、最大の負荷で川崎市で2.5倍程度の重さ。
---
## 毎日どの程度発行されるか
どの市で毎日どれぐらいのコンビニ証明書が発行されているか?公開されている数字がなかったので、
大体を考えてみます。
---
## Bardに大体の数出してみた
日本全体で1日にどれぐらいコンビニ証明書が発行されているかGoogle Bardに聞いたら、100万件とでました。
日本人はだいたい150人に1人がその日に証明書を発行していることになる。
---
## 数字的には大体正しそう。
従業員数100人の会社があったら、毎日一人、二人は役所なり免許の申請行ってるから、
数字的にそんなおかしくないかと。
---
## これを川崎市の例に当てはめてみる
この数字をそのまま川崎市にあてはめてやると、川崎市で毎日発行されているコンビニ証明書の数字がだいたいですが、わかります。
川崎市で大体毎日1万人ぐらいがコンビニ証明書を発行していることになります。
<!--
ここから、
-->
---
## 実際の負荷を仮定してみる
ここで話を簡単にするために、3つの時間に負荷が集中して、その時間以外は負荷がないとする。
---
## 負荷が大きい時間帯はこれぐらいになりそう
会社員の就業時間に合わせた、この時間帯に集中しそう。
1万人を時間帯の右の人数で分けてみる。
1. 朝の08:00~08:30 2000人
2. 昼の11:30~13:00 3000人
3. 夜の18:00~19:00 5000人
*証明書受付は毎日06:30から23:00まで
<!-- これ見て、 -->
---
## あれ負荷少なくね?
市全体で一番重い負荷でも1分間に平均80人しか問い合わせ来てない...
しかもコンビニ証明書の利用時間は決まっていて、毎日06:30から23:00間だけ申請できるとなっている。
なのでその時間以外はメンテナンスできます。
<!-- つまり、 -->
---
## よって毎日メンテナンスし放題

<!--
であり、
-->
---
## 負荷というのはそもそも嘘です
利用者が10倍になっても、今のコンピューターなら超余裕。
みなさんの手元のパソコンでも普通に処理しきれます。
レガシーなシステムでも余裕。
<!-- ですね。ニュースサイトに -->
---
## 今回のバグを修正した後に
富士⭕️japan「別の利用者がファイルをつかめてしまう仕様の詳細などについては回答を控えた」
<!--
って言ってるんですが、
https://xtech.nikkei.com/atcl/nxt/news/18/14920/ -->
---
## 真実は
排他処理ができてないだけ。

<!--
ここら辺の仕様の詳細も
-->
---
## じゃあ何の排他処理ができていなかったか?
仕様の詳細が公開されていないけど、twitterで言ってた、憶測でいいます。
<!-- ただ、だいたいあってそう。 -->
---
## 細かい仕様は公開されてないが。
一度サーバーにファイルを出力して、その後ファイルを各プリンタにダウンロードする作りで、
その出力するファイル名を時刻にしていたから、同じ時刻のファイルができた時に上書きされたのではないのかと。
<!-- -->
---
## 実際そうだったっぽい。
この後富士⭕️japanからマイクロ秒一致していたら、ファイルが上書きされるという話がさらに出たのでほぼ確定。
---
<!-- ということで、 -->
## どうやって、これを解決するか?
実際排他処理してみましょう。
---
## 解決方法でtwitterで一番みたのは
ファイルを書き込みに行くときマイクロ秒一致したら、ミリ秒待つ。
良さそうに見えますね。
<!-- やめてください。 -->
---
## 😂ある日突然システムが死んでしまいます。
一致したらずらすだとユーザー数が増えるに従って衝突回数が増える。
---
## 永遠に待っている列が無くならないシステムになる。
<!-- 例を挙げると、 -->
二人同時に書き込みに行きました。 一人書き込みます。 一人待ちます。
待った一人が書き込みに行きます。 別にまた新規に書き込みに行く人が出ました。
これで書き込みに行く人は2人になりました。またもう一人待ちます。
<!-- 待つ時間を指定しているので、 -->
---
## 負荷が増えると実質的に無限ループになる
開発の時には気にならなかったけど、リリースした後に思ったより負荷デカかったになって1、2年したら死ぬシステム。
<!-- 大規模なシステムでよく最初に入っている開発チームが作るバグ -->
最近のcloud 関係のサービスとの相性も悪い作り。

---
富士⭕️japanの記事をみると「タイムアウトを無くしたと」会社が言っているのでこれを採用したみたいです。
ですので、ユーザー数増えたら一気に負荷で落ちるかもですね。
<!-- 富士⭕️japanの記事をみると「タイムアウトを無くしたと」会社が言っているのでこれを採用したみたいです。ですので、ユーザー数増えたら一気に負荷で落ちますね。
じゃあ -->
---
## じゃあ、どうするか?
基本は排他処理を自分で書かない。考えない。
---
## 解決できる方法
<!-- 次になります -->
1. rdbmsのsequence+時刻を使う
2. uuidかそれに近いものを使う
3. あらかじめ、ユニークな値をdbに保存しておいて、排他処理
1が基本
---
2は特別な理由ない限り重くて運用負荷が大きいだけ。uuidを作成するのはまあまあ多い処理なので。
3を採用するときは2かつ3になることが多い。
<!-- 2は特別な理由ない限り重くて運用負荷が大きいだけ。uuidを作成するのはまあまあ多い処理なので。
3を採用するときは2かつ3になることが多い。
-->
---
## どのやり方もOrmでは運用が困難か、手間が大きくなる
ということで、SQL素直に書きましょう。
<!--
-->
---
## rdbmsのsequence+時刻を使う
<!-- 特長として -->
シンプルで運用エンジニアの負荷も少なく、dbの基本的かつ非常に重要な仕組みを使っているので、
db側のバグもほぼありえない。
---
<!-- sequenceってなんや?っていう人も多いと思うので -->
## sequenceの特長
<!-- 説明します。 -->
1. 1から始まって、2の64乗の値でループするrdbmsのオブジェクト。
2. 同じ時刻に違うセッションから参照しても違う値になることが保証されている。
3. 同じマイクロ秒で10の18上回処理回るならこれもアウトだけど、証券会社でもそんなこと起きないので、大丈夫
[postgresql sequence](https://www.postgresql.jp/document/15/html/sql-createsequence.html)
<!-- 2. int64の値の範囲で2の64>= 10の18乗
3. 値がたまに大きく飛ぶことがあるが、ループ内ではユニークな値のは保証されている。
4. これはormとかのidもそうだけど、たまに飛ぶmysqlでも、postgresqlでも
sequenceは作成するときに違うセッションから同時に作成しても、
違う値が作成されることがサポートされています。
[postgresql sequence](https://www.postgresql.jp/document/15/html/sql-createsequence.html)
-->
<!--
あまり使ったこともない人もいると思うので、使い方を説明します。
-->
---
## sequenceの使い方
1. 作成
```sql
CREATE SEQUENCE hello_seq
START 1
CYCLE;
```
2. 参照
```sql
SELECT nextval('hello_seq');
```
この値と時刻をファイル名なり何なりに使ったらマイクロ秒レベルでユニークになる。
<!-- 他のやり方もサクッと説明していきます。 -->
---
## uuidかそれに近いもの
<!-- -->
これが必須な時
1. 連番が嫌。(謎に何か嫌な人がいる。)
2. ファイルシステム含む、複数のDBがリアルタイムに同期、連携取れてない時に使う。(別々の場所で同時に作成しても、被りようがないため。)
---
## あらかじめ、ユニークな値をdbに保存しておいて、排他処理
外部のシステムに依存している部分がある。
その外部のシステムの負荷が大きいから、ユニークな値をあらかじめ発行しておくので、保存しているやつを好きに使って後でバッチ処理などで連携してくれという作り。
selectするときにselect for updateでデータ取ってきて云々をやる。
<!--
具体例がないとわかりづらいので、
-->
---
## どういう時にやる必要があるか?
<!-- かというと、 -->
よくあるのは外部側のシステムの作りとして外部決済の決済番号や、シリアル番号がuuidになっていて、あらかじめ発行しておく必要があるとか。
---
## どういう時に必要?2
たいてい向こう側がcobolか古いシステム。運用負荷をこちらが担うことになるので、あまりやるべき設計でない。運用負荷が高くなる。
<!-- なんでこうなるかというと、cobolはsequence無いから、短時間に何か作る系の排他処理苦手なんですよ。必要だと感じたらやってください。 -->
---
<!--
というわけで
-->
## まとめ
<!-- に入らせて頂きますと、 -->
1. 排他処理は時間で待たない
2. 排他処理の基本はsequence
3. 要件によってはuuidを選択する必要がある。
4. ユニークな値を保管して使うのは運用負荷高い。
<!--
という事だけ覚えて頂けたら幸いです。
ご清聴ありがとうございました。
-->

{"metaMigratedAt":"2023-06-18T04:26:52.472Z","metaMigratedFrom":"YAML","title":"富士○japanの「2人同時に発行申請すると上書き」バグの原因と対処方","breaks":true,"slideOptions":"{\"controls\":false,\"slideNumber\":false,\"progress\":true}","contributors":"[{\"id\":\"13bbf9e7-416d-4813-946c-591c31f51efc\",\"add\":10933,\"del\":4220}]"}