モヒカンメモ

髪色が定期的に変わることに定評のある(比較的)若者Webエンジニアの備忘録

ISUCON10予選を何とか突破出来たンゴ〜〜〜

9/12 (土) に開催されたパフォーマンスチューニングコンテスト「ISUCON10」のオンライン予選にて、チーム「牡蠣の鋭利な殻が指に突き刺さり利き手を負傷」として予選18位で予選突破しました 🎉

f:id:pinkumohikan:20201002200217j:plain
ISUCON10 FINALIST認定証

isucon.net

ISUCONとは何なのか

Iikanjini Speed Up CONtest。お題となるWebアプリケーションをガツガツゴリゴリいじくり回して、超たくさんの人が同時に使っても死なないチョッパヤ仕様にするエクストリームイベント。

isucon.net

今年で10回目となるISUCON10には490チーム、1,209人が参加したらしい。

俺とISUCON

俺とヤツとの出会いは、そう… (割愛)

イカれたメンバーを紹介するぜ

平均年齢26歳の若手キャピキャピチームです!

cureseven

  • 歴史や思いが溢れてしまっているクエリをいい感じにしたりアプリをいじくり回す担当
  • あと何か盛り上げたりとか
  •  Go言語歴は3ヶ月ぐらい
  • リアルでは半年以上会ってなかったけど先週会った
  • 最近転職したらしい

twitter.com

shiningcureseven.hatenablog.com

nekodaisuki

  • 担当: インフラをバンバンしばき回す担当...なはずだったけど最終的にコードを書くことになりがち担当
  • pinkumohikanと白熱バトルをしてcuresevenを引かせること多数
  • インフラ歴は4ヶ月ぐらい
  • リアルではもう半年以上会ってない
  • 予選における本チームのMVP。例のなぞって検索を論文読んでチューニングしたすごいやつ
  • 最近転職したらしい

twitter.com

sinnderu.hatenablog.com

pinkumohikan

  • どこをいい感じにするのが良さそうか調べたり考えたり祈ったりする担当
  • ISUCON歴は多分チーム内で一番長いので敬って欲しい
  • 最近転職していない

twitter.com

やっていきのスタンスとか

もう講評も出ているのでどこを直したかとかはあんまり書いてもしょうがないので施策についての紹介は割愛するんですが、基本戦略としてはパフォーマンスチューニングの原則に従っていろいろ「計測」して、「遅いところを改修」して、「早くなったか確認」、をガンガン回していく感じで臨みました。

講評で紹介されていたDBサーバを増やす、nazotteをいい感じにする、indexいい感じにする、あたりは概ね出来ていたかと思います。nazotteをいい感じにする辺りはnekodaisuki氏が論文を読んでアプリケーション側で解決するようなコードに書き直していて草でした。Generated columnとか空間indexは知らなかったので純粋に学びが有った。

isucon.net

これは秘密なんですが、限られた時間でいい感じにしていくにはN+1や露骨なロックみたいな "いかにも" な点を見つけても そこがその時点でのボトルネックとなっていなければスルー するのが大事です。

0点のまま突き進んだ3時間

実は、ベンチマーカーの不具合により弊チームは最後の3時間ぐらいはずっとスコア0点の状態で競技を続けて、最終スコアも0点だったんですよねw

大きめな施策の準備が終わってベンチ回したところ、特にエラーは出ていないもののベンチがfailするようになりました。何も情報が出てなくて謎なので運営に問い合わせると詳細なログを出してくれたんですが、ログにはsearch系endpointのタイムアウトしか出ておらず。当日マニュアルの スコア計算 ルールによればこの場合はfailとはならないはずなので、その旨を運営に連絡して競技を続けました。

ISUCON10 予選マニュアル · GitHub

また、一定時間内にレスポンスが返却されない場合はリクエストが中断され、最後に (タイムアウトしました) の付いたメッセージが表示されます。 これによるイス・物件の CSV 入稿以外の API での失格・減点はありません。

その後「状況を確認して対応を検討する」旨の返答がありました。「ルール的にはfailでないが、競技開始後なので公平性のためベンチマーカーは改修しない」とされる可能性はあったものの、15時ぐらいにベンチマーカーが一度改修がされていた実績があるので一度あることは二度あるやろ!と思って競技を続けました。結果、そうなったのでよかったーーー。

isucon.net

予選レギュレーション上は、"競技時間内の最後に記録されたスコア" (最終スコア) を本選出場チームの選出に利用する、と定めています。しかし、本不具合を鑑み、最終スコアが 0 点であったが、再起動を伴う追試にて予選最終結果 25 位のスコアを上回ったチームについて、最終スコアが本不具合によるものではないかを確認しました。

その結果、以下のチームについて、本不具合がなければ最終スコアが 0 点となっておらず、本来であれば本選出場チームに含まれていたと考えられるため、運営チームで協議の結果、別途本選出場チームとして選出することにいたしました。

博打といえば博打なんですが、failしない状態のまま終えてもスコア的に予選通過出来る見込みが無かったのでダメ元で賭けてみた感じです。

…7割ぐらいはダメだろうなって思ってたので、下記のようなtweetもしていますw

やる気アゲアゲマックスだった二人に感謝

弊チームはチーム結成から予選当日までの約3ヶ月間で、合計9回の練習をしました。

f:id:pinkumohikan:20201002200833j:plain
GitHub Repositories

正直僕はここまで練習頑張るモチベーションは無かったんですが (爆弾発言)、cureseven氏とnekodaisuki氏が「やるぞ!!!!」「うおおおおおおおお!100万円!!!」みたいなテンションだったのでそれに引っ張られる形で毎週末練習をしていたら徐々にみんなの改修力がいい感じにレベルアップしていき、いい感じの成果を出せるようになっていました。

地道な練習が功を成していて、いい話だなあ。

おじさん ベテランチームには負けないぞ〜〜〜

本戦への意気込み

僕は「行けるところまで行こう!」って感じなんですが、cureseven氏とnekodaisuki氏の二人は燃えています。

また引っ張ってもらいます。

f:id:pinkumohikan:20201002202738p:plain
おまけ

なんかおこられたw

CentOS8へremiリポジトリを使ってPHP7.4をインストールする

デフォルトリポジトリを使ってPHPをインストールするとCentOS 7系では5.4が、CentOS8系ではPHP 7.2が入る。それよりも新しめなバージョンのPHPを使いたいときの王道的な方法として、remiリポジトリを使う方法を紹介する。

f:id:pinkumohikan:20200920002804p:plain
PHP 7.4

前提

  • 2020/09/20 (日) 時点の情報
# cat /etc/redhat-release
CentOS Linux release 8.2.2004 (Core)

本記事ではCentOS 8を前提に書いているが、CentOS 7 / Amazon Linux 2でも大体同じような手順でインストールできる。CentOS 7で行う場合は dnfyum と読み替えればOK。

ざっくり手順

  1. remiリポジトリを使えるようにする
  2. remiリポジトリを使って、PHP 7.4をインストールする
  3. "php" で呼べるようにする

コマンドまとめ

sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm

sudo dnf install php74

sudo alternatives --install /usr/bin/php php /usr/bin/php74 1

一つづつ解説していく。

解説

1. remiリポジトリを使えるようにする

rpms.remirepo.net

remiはイカしたサードパーティ製ライブラリを提供するrpmリポジトリ。ガンガン行こうぜ系OSのFedoraで使われているような新しいバージョンのソフトウェアをCentOSとかでも使えるようにしてくれている。

$ sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-8.rpm
...
インストール済み:
  remi-release-8.1-2.el8.remi.noarch

完了しました!

URLでお察しの通りCentOSのメジャーバージョンごとにRPMファイルが違うので、CentOS 7とかの場合は remi-release-7.rpm とする。詳しくは下記を参照。

English : Repository Configuration - Remi's RPM repository - Blog

2. PHP 7.4を入れる

あとはお目当てのものをインストールしていくだけ。remiリポジトリでインストール可能なパッケージのいくつかはデフォルトリポジトリとコンフリクトしうるので無効化されていて、以前はPHP 7.4も --enablerepo=remi のように明示的に有効化する必要があった。今は remi-safe にカテゴライズされたことによって、特にオプションなしでインストールできる。

$ sudo dnf install php74
...

インストール済み:
  environment-modules-4.1.4-4.el8.x86_64                      php74-1.0-3.el8.remi.x86_64
  php74-php-cli-7.4.10-1.el8.remi.x86_64                      php74-php-common-7.4.10-1.el8.remi.x86_64
  php74-php-json-7.4.10-1.el8.remi.x86_64                     php74-runtime-1.0-3.el8.remi.x86_64
  policycoreutils-python-utils-2.9-9.el8.noarch               scl-utils-1:2.0.2-12.el8.x86_64

完了しました!

php74 というコマンド名でインストールされていることが確認できる。

$ php -v
-bash: php: コマンドが見つかりません

$ php74 -v
PHP 7.4.10 (cli) (built: Sep  1 2020 13:58:08) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies

php-mbstring などのPHP拡張を入れたいときは php74- というprefixをつける。例えば php-mbstring の場合は php74-php-mbstring といった具合。

$ dnf install php74-php-mbstring
...

インストール済み:
  oniguruma5php-6.9.5+rev1-2.el8.remi.x86_64                php74-php-mbstring-7.4.10-1.el8.remi.x86_64

完了しました!

3. "php" で呼べるようにする

PHPを使いたいときに php74 って読み替えるのは面倒なので php で呼べようにしたい。alternativesでエイリアスを設定しておくのがオススメ。

www.redhat.com

syntaxは

alternatives --install <リンク> <名前> <パス> <優先度>

なので、こんな感じにすればOK。

$ which php74
/usr/bin/php74

$ sudo alternatives --install /usr/bin/php php /usr/bin/php74 1

$ php --version
PHP 7.4.10 (cli) (built: Sep  1 2020 13:58:08) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies

Redash SaaSからRDSにアクセスするために調べたこと、考えたことまとめ

背景

RDB等にあるデータストアへクエリを投げてデータをチラ見したり、取ってきたデータをいい感じにグラフとかで可視化したり出来るRedashという便利ツールがある。ベース部分はOSSで、self hostingすることもできるし有償でSaaSとして使うこともできる

redash.io

とあるAWS RDS上のデータをパパっと可視化したくなったので、Redash SaaSからRDSへつなげるように出来るんだっけ、とか、どんなリスクがあるんだっけ、みたいなことを調べたのでまとめておく

f:id:pinkumohikan:20200823183745p:plain
redash

ざっくり要点

  • RDSのPublicly Accessibleという設定を有効にすれば、インターネット (VPC外) からRDSへアクセスできる
  • DBをinternet publicにすることで不正アクセスの危険は高まるが、既存の仕組みでリスクはある程度抑えられる

インターネット (VPC外) からRDSへアクセスすることはできる

インターネットからRDSへアクセスする場合、踏み台用のホストを用意してそのホスト経由 (SSHポートフォワーディング) でRDSへアクセスする方法が王道だが、Redash SaaSでは踏み台をサポートしてないのでこの方法は使えない

RDSには Publicly Accessible という設定があり、これを有効にするとVPC外からでもRDSへアクセスできるようになる

aws.amazon.com

dev.classmethod.jp

想定されるリスク

データベースをinternet publicにするリスク

インフラ設計においてデータベースはDMZとかprivate networkなどと呼ばれるような、インターネットから直接アクセス出来ない領域に置くのがベストプラクティスとされてきた。なので、セキュリティに関心のある開発者だと「DBをinternet publicにするのって危険じゃん」って思うと思う

不正アクセスによってDB上のデータが漏洩したり書き換えられたりするリスクが考えられるが、次のような対策を行うことでリスクはだいぶ減らせそう

  • 接続元IPアドレスによるフィルタリングを行う
  • 特に機微な情報は、暗号化した上でDBに乗せる
  • Redash等readのみしかしないDBクライアントには、権限を絞ったreadonlyなDBユーザを用意する

SaaS固有のリスク

SaaSからinternet経由でDBへアクセスする場合、次のようなリスクが考えられる

  1. SaaSそのものが攻撃を受ける可能性
    • SaaSに登録した認証情報を使い、SaaSのIPからDBアクセスされうる
  2. SaaSを踏み台として使われる可能性
    • 攻撃者がSaaSを正規に契約することで、SaaSのIPからDBアクセスされうる (IPアドレスベースのフィルタリングを回避される)

SaaS事業者もそのような攻撃を許せば面目丸つぶれなので何らかの対策を講じていると思うがリスクはゼロではない。「こういうリスクが想定されるけど、対策してる?」とか聞いて、対策が甘いなって感じたらSaaSを使わないことを検討したい

もっと対策するなら

分析専用のDBを用意する

アプリケーション用のDBとは別に分析専用のDBを用意し、想定している分析に必要なデータのみをmigrationする仕組みを作り、Redash SaaSからはそれを使うようにする。そうすれば、仮に不正アクセスされても被害範囲を限定できる

Redash SaaSを使わず、Redashを自前運用する

VPC内に自前のRedashを立ててしまえばDBをinternet publicにする必要がなくRedashを使うことが出来る

redash.io

実際、身近なWeb系企業ではこの方法を取っているところも多い。難点としてはRedashは割とカジュアルにハングするので定期的な復旧作業が生じるのと、バージョンアップに追従する更新w運用コストがちょっと掛かること

その他、細かい話

Publicly Accessibleって後から変更できる?ダウンタイムは?

2020年8月現在、AWS RDS for Aurora (MySQL互換) にて検証した限りでは あとから変更可能 で、変更によるダウンタイムは なし だった

2015年4月時点ではインスタンス作成時のみ設定可能だった模様

http://dev.sukimash.com/aws/rds-publicly-accessible-setting-change/

早速 Publicly Accessible の設定をON にすべく、いろいろと調べてみたところ、どうやらこの設定は後から変更することはできないようです。。。

従って、接続できるようにするには、RDS のスナップショットを取り、別のインスタンスを Publicly Accessible ON で起動し、そちらにつなぎ直す、という手順が必要なようだ、、、ちょっとめんどくさいですね。

mysqlコマンドの実行結果から枠線とカラム名を除いて表示する

mysqlコマンドの実行結果から枠線とカラム名を取り除いて表示する方法を調べたので備忘録がてらまとめておく

やりたいこと

ヘルスチェックや定点観測用の即席スクリプトを書くときなど、mysqlコマンドの実行結果をスリムに表示したいことがある

何もオプションを指定せずにmysqlコマンドでクエリを実行すると、下記のように枠線とカラム名がついてくる

$ mysql --host 127.0.0.1 -u root -e 'select "Hello, world" as "greeting"';
+--------------+
| greeting     |
+--------------+
| Hello, world |
+--------------+

ただデータを眺める分にはこれで良いが、別のプログラムに食わせたいときや値だけが欲しいときに枠線やカラム名が邪魔になる

なので、枠線やカラム名を取り除いて表示させたい

実行結果から枠線を消す方法

mysqlコマンドに --silent というオプションがあり、これを使うと枠線をommitして表示することができる

help:

$ mysql --help
...
  -s, --silent        Be more silent. Print results with a tab as separator,
                      each row on new line.
...

before

$ mysql --host 127.0.0.1 -u root -e 'select "Hello, world" as "greeting"';
+--------------+
| greeting     |
+--------------+
| Hello, world |
+--------------+

after

$ mysql --host 127.0.0.1 -u root --silent -e 'select "Hello, world" as "greeting"';
greeting
Hello, world

実行結果からカラム名を消す方法

mysqlコマンドに --skip-column-names というオプションがあり、これを使うとカラム名をommitして実行結果のみを表示することができる

help:

$ mysql --help
...
  -N, --skip-column-names
                      Don't write column names in results.
...

before

$ mysql --host 127.0.0.1 -u root -e 'select "Hello, world" as "greeting"';
+--------------+
| greeting     |
+--------------+
| Hello, world |
+--------------+

after

$ mysql --host 127.0.0.1 -u root --skip-column-names -e 'select "Hello, world" as "greeting"';
+--------------+
| Hello, world |
+--------------+

実行結果から枠線とカラム名を両方消す方法

単純に先の2つのオプションを同時に指定すれば良い

before

$ mysql --host 127.0.0.1 -u root -e 'select "Hello, world" as "greeting"';
+--------------+
| greeting     |
+--------------+
| Hello, world |
+--------------+

after

$ mysql --host 127.0.0.1 -u root --silent --skip-column-names -e 'select "Hello, world" as "greeting"';
Hello, world

余談

mysqlコマンドにパイプで続けてコマンドを書くと、実行結果から枠線は取り除いて渡してくれる

小さなプログラムを組み合わせて大きなプログラムを作るための工夫で、Linux哲学ってやつ

続くコマンドなし

$ mysql --host 127.0.0.1 -u root  -e 'select "Hello, world" as "greeting"'
+--------------+
| greeting     |
+--------------+
| Hello, world |
+--------------+

続くコマンドあり

$ mysql --host 127.0.0.1 -u root  -e 'select "Hello, world" as "greeting"' | cat
greeting
Hello, world

GitHub Activityをグラフで可視化してくれるGitHub Readme StatsでGitHub映えを狙う

こんにちは、可視化大好きお兄さんこと @pinkumohikan です(自称)。

Webエンジニア御用達のソースコード管理ツール "GitHub" でのアクティビティを可視化してくれる GitHub Readme Stats というおもしろツールが、結構映えるグラフを作ってくれていい感じだったのでご紹介します。

f:id:pinkumohikan:20200803022415p:plain

GitHub Readme Stats

github.com

基本的には

https://github-readme-stats.vercel.app/api?username=pinkumohikan

みたいなURLにアクセスすると動的にActivityを集計してグラフ表示してくれます。獲得したスター数や、当年のスター数、作ったPR数、作ったIssue数など。色々カスタマイズも出来るみたい。

自分は下記のようにオプションを設定しており、

https://github-readme-stats.vercel.app/api?username=pinkumohikan&count_private=true&show_icons=true&theme=radical

するとこんなふうなグラフがでます。

https://github-readme-stats.vercel.app/api?username=pinkumohikan&count_private=true&show_icons=true&theme=radical

Special Repositoryに置くのがオススメ

GitHubの自分のアカウント名と同じリポジトリを Special Repository と呼ぶらしいです。例えばアカウント名が pinkumohikan なら pinkumohikan というリポジトリがそれに当たります。

f:id:pinkumohikan:20200804111353p:plain
ひみつだよ!

github.com

そしてなにやら、Special RepositoryのREADMEに書いた文章はプロフィールページにもサマリー表示されるらしい。

github.com

こんな感じ: f:id:pinkumohikan:20200803022125p:plain

GitHub Readme StatsのURLをREADMEに貼っとけば GitHub映えしますね。僕のREADMEにはさっき紹介したGitHub Activityに加えて、良く使う言語のグラフも表示しています。

パパっと真似したいかたは

あんまりオプションとか関心なくてとりあえず真似してみたいかたは、僕のREADMEをコピペしてもらえればそれっぽいものがシュッと出来上がります。

Raw README.md: https://raw.githubusercontent.com/pinkumohikan/pinkumohikan/master/README.md

pinkumohikan のところを自分のGitHubアカウント名にreplace、自分のGitHubアカウント名でリポジトリを作り、そこにREADME.mdとして保存すればパパっと試せます。お試しあれ。

useraddコマンドをAmazon Linux2へインストールする

Linuxユーザを追加するときに使うuseraddコマンド。Amazon Linux2のdockerイメージではデフォルトでは入っていなかったのでインストール方法を調べたたときの備忘録。Amazon Linux2に限らず、Linuxのminimal環境にuseraddコマンドを入れるのに使える方法だと思う。

useraddコマンド

 「useradd」は新規ユーザーを作成し、ユーザーごとの設定を決めるコマンドです。作成時のデフォルト値をあらかじめ定めておくこともできます。

www.atmarkit.co.jp

Amazon Linux2 dockerイメージ

Amazon Web ServicesのEC2でインスタンスを立てるときによく使われる Amazon Linux2 のdocker imageがdockerhubに上がっていた。

hub.docker.com

f:id:pinkumohikan:20200704121521p:plain
Amazon Linux2のdocker image

$ docker run --rm -it amazonlinux:2 bash
bash-4.2# cat /etc/os-release | grep PRETTY_NAME
PRETTY_NAME="Amazon Linux 2"

ただ、実際のAmazon Linux2 AMIとはインストールされているパッケージとかが若干の差異があるみたい。sudoとかuseraddとかsystemdとか入ってないし、minimal版みたいな感じっぽい。

デフォルトではuseraddコマンドが入っていない

Amazon Linux2向けのプロビジョニングを書いていて、ユーザの追加とかやりたかったんだけどuseraddコマンドが入っていない。

bash-4.2# useradd
bash: useradd: command not found

適当にググったら shadow-utils っていうパッケージに内包されているらしい。

シャドウパスワードファイルとユーザー/グループアカウントを管理するための ユーティリティ。

penguin.triumf.ca

$ docker run --rm -it amazonlinux:2 bash
bash-4.2# cat /etc/os-release | grep PRETTY_NAME
PRETTY_NAME="Amazon Linux 2"
bash-4.2#
bash-4.2#
bash-4.2# yum list shadow-utils
Loaded plugins: ovl, priorities
amzn2-core                                                         | 3.7 kB  00:00:00
(1/3): amzn2-core/2/x86_64/group_gz                                | 2.5 kB  00:00:00
(2/3): amzn2-core/2/x86_64/updateinfo                              | 223 kB  00:00:00
(3/3): amzn2-core/2/x86_64/primary_db                              |  42 MB  00:00:03
Available Packages
shadow-utils.x86_64                   2:4.1.5.1-24.amzn2.0.2                    amzn2-core

yumリポジトリの追加要らずでシュッとインストールできそう。

shadow-utilsをインストールする

bash-4.2# yum install -y shadow-utils
Loaded plugins: ovl, priorities
amzn2-core                                                         | 3.7 kB  00:00:00
(1/3): amzn2-core/2/x86_64/group_gz                                | 2.5 kB  00:00:00
(2/3): amzn2-core/2/x86_64/updateinfo                              | 223 kB  00:00:00
(3/3): amzn2-core/2/x86_64/primary_db                              |  42 MB  00:00:04
Resolving Dependencies
...
Installed:
  shadow-utils.x86_64 2:4.1.5.1-24.amzn2.0.2

Dependency Installed:
  audit-libs.x86_64 0:2.8.1-3.amzn2.1         libcap-ng.x86_64 0:0.7.5-4.amzn2.0.4
  libsemanage.x86_64 0:2.5-11.amzn2           ustr.x86_64 0:1.0.4-16.amzn2.0.3

Complete!

useraddが使えるようになった🎉

bash-4.2# useradd
Usage: useradd [options] LOGIN
       useradd -D
       useradd -D [options]

Options:
...

めでたしめでたし

80番ポートを使っているプログラムをlsofコマンドで特定する

Linux環境で特定のポートを使いたいけど先に使われていてどのプログラムがそのポートを使っているか知りたいとき、lsofコマンドが便利。

lsofコマンド

 「lsof」はオープンしているファイルを一覧表示するコマンドです。

www.atmarkit.co.jp

lsofコマンドに-iオプションでポート番号をつけて実行すると、そのポートを握っているプログラム (コマンド) がわかる。

80番ポートを使っているのは誰?

$ sudo lsof -i:80
COMMAND   PID     USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME
nginx   17287     root   12u  IPv4 47601898      0t0  TCP *:http (LISTEN)
nginx   17287     root   14u  IPv6 47601900      0t0  TCP *:http (LISTEN)
nginx   17292 www-data   12u  IPv4 47601898      0t0  TCP *:http (LISTEN)
nginx   17292 www-data   14u  IPv6 47601900      0t0  TCP *:http (LISTEN)
nginx   17293 www-data   12u  IPv4 47601898      0t0  TCP *:http (LISTEN)
nginx   17293 www-data   14u  IPv6 47601900      0t0  TCP *:http (LISTEN)

この例ではnginxがTCP 80番 (http) を握っていることが読み取れる。

Nginxに "413 Request Entity Too Large" と怒られる原因と対処法

Nginxをリバースプロキシとして使っている環境でファイルアップロードなどを行おうとしたとき "413 Request Entity Too Large" というエラーレスポンスが返ることが有る。

nginx.org

f:id:pinkumohikan:20200526235206j:plain
413 Request Entity Too Large

原因

Nginxへ送信されたコンテンツのサイズが大きすぎて、制限を超えているため。

HTTP Response Status Code 413の定義は下記:

6.5.11. 413 Payload Too Large

The 413 (Payload Too Large) status code indicates that the server is refusing to process a request because the request payload is larger than the server is willing or able to process. The server MAY close the connection to prevent the client from continuing the request.

tools.ietf.org

意訳: 413はリクエストがでかすぎてサーバが拒否ったことを示すステータスコードだよ。

※ RFC的には "Payload Too Large" って定義されているけど、Nginxは "Request Entity Too Large" って返してるの何でだろう?知っている人いたら教えて下さい。

対処法

方法としてはざっくり、制限内で収まるようにリクエストサイズを制限する方法と、Nginxの設定を変更してリクエストサイズの上限を引き上げる方法がある。

1. 制限内で収まるようにリクエストサイズを小さくする方法

レンタルサーバを使っている場合など、Nginxの設定を変更する権限がない場合にはこうするほか無さそう。現状の設定を確認して、それに収まるようにリクエストサイズを小さくする。

例えばファイルアップロード機能で怒られる場合は、アップロードできるファイルサイズをNginxが許容するサイズ以下になるようUI側で制限する。具体的な方法としてはFile APIなどが使えそう。

developer.mozilla.org

2. Nginxの設定を変更してリクエストサイズの上限を引き上げる方法

Nginxの設定を変更する権限がある場合、設定ファイルの client_max_body_size というパラメータを変更することでリクエストサイズの上限を引き上げられる。デフォルトでは1MBと結構タイト。

nginx.org

Syntax: client_max_body_size size;

Default: client_max_body_size 1m;

Context: http, server, location

Sets the maximum allowed size of the client request body, specified in the “Content-Length” request header field. If the size in a request exceeds the configured value, the 413 (Request Entity Too Large) error is returned to the client. Please be aware that browsers cannot correctly display this error. Setting size to 0 disables checking of client request body size.

意訳: クライアントが送ってくるリクエストボディサイズの上限を設定する。リクエストのContent-Lengthが設定値を超えていたら 413 (Request Entity Too Large) エラーをクライアントへ返すよ。0を指定したらサイズをチェックしないよ。

ただし、設定を変更する前に、現状の設定がどういった意図でされているものかを確認するべし。単にデフォルトのままとかであれば変えて良さそうけど、例えばサーバのスペックと想定同時アクセス数などを勘案してリクエストサイズを制限している場合、むやみに設定を変えるとサーバがトラフィックに耐えられなくなる恐れがある。

f:id:pinkumohikan:20200526234900p:plain
50MBまで許す設定の例

設定を書き換えてNginxをreloadしてあげれば、もう怒られない。

f:id:pinkumohikan:20200526235219j:plain
200 OK。もう大きなファイルを上げても怒られない

FileReader.readAsDataURLで得られるのはData URIであって純粋なbase64文字列じゃないぞ

ファイルをFileReader.readAsDataURLを使って文字列化したものは純粋なbase64文字列だと思っていたけど、decodeしようとしたら出来なくてなんでやねん!と思ったので残しておく。

まとめ

  • 画像とかの添付ファイルをJavaScriptで扱うときにFileReaderを使う
  • FileReader.readAsDataURLのresultで得られるのはData URIであって、純粋なbase64文字列じゃない
  • 先頭のData URI宣言部を取り除いたら純粋なbase64文字列になる

大事なことは全部ドキュメントが教えてくれた

base64 decodeできなくて「おや?」と思ったけど、大事なことは全部ドキュメントに書いてあった。

developer.mozilla.org

メモ: blob の result は、先に Base64 でエンコードされたデータの前にある Data-URL の宣言を削除しておかないと、直接 Base64 としてデコードすることができません。 Base64 でエンコードされた文字列のみを受け取る場合は、先に結果から data:/;base64, を削除しておく必要があります。

resultの文字列をprintしてみたら data:image/jpeg;base64,/9j/2wCEAAgGB... みたいな感じになっていて、たしかにbase64文字列じゃないや。っていうかよくよく考えればメソッド名が readAsDataURL だから、実行結果として得られるものは当然Data URIだよな... 😇

余計な文字列を取り除く

文字列化したものをWeb API等へ送るときは、先頭の余計な文字列を取り除いておこう。

実際に取り出した文字列をみるに data:${mimeType};base64,${base64EncodedFile} という構造になっていそうだけど、ドキュメントに 先に結果から data:*/*;base64, を削除しておく必要があります とあるのでとりあえずそれに従えば良さそう。

こんな感じ:

const dataURI = filereader.result; // "data:image/jpeg;base64,/9j/2wCEAAgGB..." みたいなのが入る
const base64EncodedFile = dataURI.replace(/data:.*\/.*;base64,/, '');

developer.mozilla.org

海外へWebコンテンツを配信する際はアクセシビリティの法律に気をつけるべし

ふとしたtweetをきっかけに、海外向けにWebコンテンツを配信する際には現地のアクセシビリティに関する法律に気をつける必要があることを学んだ。

ざっくりまとめ

  • アメリカ等、国によってはアクセシビリティが権利として法的に認められている
  • 先の法を根拠に年間2000件以上の訴訟が起きている
  • 日本ではアクセシビリティは努力目標だけど、同じ感覚で海外展開すると危険

アクセシビリティとは

情報システムの利用しやすさを表す言葉。パソコンやインターネットが幅広く普及した現在、高齢者や障害者などハンディを持つ人にも健常者と同じように使える環境を整える必要がある。アクセシビリティはその対応度を計る尺度となるもの。具体的には「画像や音声などには代替表現として必ずテキストによる注釈をつける」「マウスの使用が困難な人向けにすべての操作をキーボードで行なえるようにする」ことなどが求められている。

kotobank.jp

ざっくり言うと、みんながみんな同じように画像を認識し、文字が読めるわけではないから、そういう境遇の人でもコンテンツを享受できるように配慮してね、みたいなやつ。マルチデバイス対応とか音声読み上げへの対応が代表例。

日本にも、Webコンテンツのアクセシビリティに関するJIS規格があるらしい。

waic.jp

国によってはアクセシビリティが権利として認められている

webtan.impress.co.jp

今回の学びの大部分は上記記事からのもの。特に学びが有ったところを下記に抜粋。

アメリカには、障害を持つ人がアメリカ社会に完全に参加できることを保証したADA(Americans with Disabilities Act 障害を持つアメリカ人法)という法律があります。このADAに基づいたWebアクセシビリティ提訴の件数が、2017年には814件だったのが2018年には2,285件となり281%も増加しています。

...

弁護士の手配や証拠開示手続き、交渉などを含めると、訴訟1件あたりの費用の試算は356,775ドル(約4,000万円)にものぼるとのことです。

...

アメリカでは、5人に1人(=6,400万人)がなんらかの障害を持っており、障害のある就労年齢の人々の可処分所得合計は約4,900億ドル。アフリカ系アメリカ人は5,010億ドル、ヒスパニック系アメリカ人は5,820億ドルとなっており、他の重要な市場セグメントに匹敵することことも紹介していました。

...

アメリカの場合は法律で「やれ」と定められており、アクセシビリティ対策をしないという選択肢はそもそもないらしい。というよりむしろマーケットとしてそれなりに大きいのでアクセシビリティへの積極投資に経済的合理性があるらしい。

こと日本においてはアクセシビリティ対策は努力目標の位置づけだけど、海外では権利として認められているところもあるので海外展開する際に日本と同じ感覚でいると危ないことは覚えておきたい。