Elastic BeanstalkにRailsアプリケーションをデプロイした
Elastic BeanstalkでRailsアプリケーションを動かしたので、ハマったところ等をまとめました。
用途は少し限定されますが、負荷も大きくないような簡単なアプリケーションを動かすには良い選択肢になるかなと思いました。
特徴/メリット
- Dockerfileがあるアプリケーションを簡単に構築できる(※ 他の言語もサポートされてる)
- Nginxなどのwebサーバーの設定がほとんど不要なので、アプリケーションさえあれば動かせる
- デプロイフローの構築も割と簡単
デメリット
- 設定等にハマると、普通に構築した方が早いと思った
- ログがあまり充実してなさそうなので、要確認
- ガッツリ本番でリクエストを捌くレベルでは、心もとなく感じた
環境
インフラ構成
デプロイ
GitHub Actionsを利用しました。
- Github ActionsでDockerのビルド
- imageをECRにpush
- Elastic Beastalk上で動かすDockerfile等の作成
- 作成したDockerfileをElastic Beastalkへデプロイ
※ 事前にAWSでデプロイユーザを作成し、そのクレデンシャルをGitHubのsecretsで設定しています。
name: AWS Elastic Beanstalk Deploy on: push: branches: - production env: AWS_REGION: ap-northeast-1 AWS_ECR_REPO_NAME: application-name jobs: delivery: runs-on: ubuntu-latest timeout-minutes: 15 steps: - name: Checkout the repository uses: actions/checkout@v1 - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: ${{ env.AWS_REGION }} - name: Login to Amazon ECR id: login-ecr uses: aws-actions/amazon-ecr-login@v1 - name: Build a docker image, and push to Amazon ECR env: ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }} run: | docker build -t $ECR_REGISTRY/$AWS_ECR_REPO_NAME:${{ github.sha }} . docker push $ECR_REGISTRY/$AWS_ECR_REPO_NAME:${{ github.sha }} - name: Prepare for Beanstalk deployment env: DOCKER_IMAGE_URL: ${{ steps.login-ecr.outputs.registry }}/${{ env.AWS_ECR_REPO_NAME }}:${{ github.sha }} run: | cd elastic_beanstalk && ./build_dockerfile.sh && zip -r ./app-${{ github.sha }}.zip ./ - name: Deploy web to EB uses: einaregilsson/beanstalk-deploy@v14 with: aws_access_key: ${{ secrets.AWS_ACCESS_KEY_ID }} aws_secret_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} application_name: beanstalk-application environment_name: app-env wait_for_environment_recovery: 90 version_label: app-${{ github.sha }} region: ${{ env.AWS_REGION }} deployment_package: elastic_beanstalk/app-${{ github.sha }}.zip
Prepare for Beanstalk deployment
のステップでは、以下のようにDockerfileのimageのURLを書き込んだりしています。
FROM {{DOCKER_IMAGE_URL}} ENV RAILS_ENV=production EXPOSE 3000 ENTRYPOINT [""] CMD ["/usr/bin/docker_init.sh"]
インフラ構築
ドメイン
こちらで無料で取得できました。
Elastic Beanstalk周りでハマったところ
Nginxの設定
AWSの公式ドキュメントや、その他の記事を見ていると .ebextentions
配下に設定ファイルを置けば読み込まれると言った情報が見られましたが、以下のように .platform
配下に置く必要がありました。 .ebextentions
でも実行自体はされるみたいでしたが、Elastic Beanstalkのライフサイクルの都合で、更に上書きされてしまうようでした。
10MB以上のファイルをアップロードすると、413エラーが返ってくる状態だったため、 client_max_body_size
を設定しました。
$ tree -a elastic_beanstalk elastic_beanstalk ├── .platform │ └── nginx │ └── conf.d │ └── client-max-body-size.conf └── Dockerfile $ cat elastic_beanstalk/.platform/nginx/conf.d/client-max-body-size.conf client_max_body_size 30m;
他にも、デプロイ時に実行するフックなどを設定できます。
Sidekiq
今回の用途では、そこまで高負荷でもなく、Sidekiq用にコンテナの数を増やすほどでもなかったので、少し力技で同一のコンテナ上で動かしました。
docker_init.sh
#!/bin/sh rm -f tmp/pids/server.pid && \ rails db:migrate && \ rails assets:precompile && \ bundle exec sidekiq & \ rails server -b 0.0.0.0
「マイクロサービスパターン 実践的システムデザインのためのコード解説」を読んだ
マイクロサービスパターン[実践的システムデザインのためのコード解説] (impress top gear)
- 作者:Chris Richardson
- 発売日: 2020/03/23
- メディア: 単行本(ソフトカバー)
目的、モチベーション
マイクロサービスのデザインパターンを知りたかった。
全体の感想
サーガのパターンを扱っていたのが特に参考になった。全体的には、具体的な仮想のプロジェクトをもとにコードの事例も記載されており、抽象的にも具体的にも説明されていて、バランスが良かった。 マイクロサービスアーキテクチャと重複している箇所も多かったので、読んだことのある人は数章だけ読むので十分だと感じた。
目次
- 目的、モチベーション
- 全体の感想
- 目次
- 概要
- 次のアクション
概要
Chapter 1 モノリシック地獄からの脱出
1.4. マイクロサービスアーキテクチャで状況打開
1.4.1. スケールキューブとマイクロサービス
マイクロサービスはArt of Scalability, The: Scalable Web Architecture, Processes, and Organizations for the Modern Enterprise (Pear04)で紹介されているスケールの3次元モデルの軸の一つと言える。
Chapter 2 サービスへの分割
DDDとかに基づいてドメイン分割
Chapter 3 マイクロサービスアーキテクチャで使われるプロセス間通信
3.2 同期的なリモートプロシージャ呼び出しパターンを使った通信
3.3 非同期的メッセージングパターンを使った通信
3.3.4 メッセージブローカー
ブローカーレスメッセージング
- メリット
- デメリット
ブローカーベースメッセージング
技術選定の際には、以下のようなことを考慮しなければならない。
- サポートされているプログラミング言語
- サポートされているメッセージング標準
- メッセージの順序
- 永続記憶
- 持続性
- スケーラビリティ
- レイテンシ
競合するコンシューマ
メリット
- 疎結合
- メッセージのバッファリング
- 柔軟性の高い通信
- 明示的なIPC
- デメリット
- パフォーマンスのボトルネックになる危険性
- 単一障害点になる危険性
- 運用の複雑度の上昇
3.3.7 トランザクショナルメッセージング
DBの書き込みのトランザクション内で、メッセージをパブリッシュしたいケースがある。パフォーマンスと信頼性を担保するためのパターンがいくつかある。
Transactional outbox
DBにOUTBOXというテーブルを追加して、書き込みのトランザクション内でOUTBOXテーブルにメッセージを挿入する。メッセージリレーがOUTBOXテーブルを読み込み、メッセージブローカーにメッセージをパブリッシュする。
このようにすることで、トランザクションをローカルに留め、アトミック性を保証することができる。
Polling publisher
メッセージリレーが、OUTBOXテーブルをポーリングしてパブリッシュするパターン。
Transactional log tailing
Transaction Log MinerがOUTBOXテーブルのトランザクションログを読み込み、メッセージに変換してパブリッシュする。トランザクションをサポートしていないNoSQLでも使用できる。
以下のようなプロジェクトで採用されている。
- Debezium: DBの変更をApache KafkaメッセージブローカーにパブリッシュするOSS。
- LinkedIn Databus
- DynamoDB streams
- Eventuate Tram
Chapter 4 サーガによるトランザクションの管理
4.2 サーガのコーディネート
4.2.1 コレオグラフィベースのサーガ
中央で管理するサービスがなく、それぞれのサービスがメッセージをpublishやconsumeする。問題は大きく2点ある。
- DBのトランザクションとイベントのpublishをアトミックに行わなければならない
- 相関するデータに対応するために、関連するIDが必要になる
欠点
- サーガの定義が分散されているので、わかりにくい
- サービスが循環依存してしまうことがある
- 関連するイベントをサブスクライブしないといけないので、密結合になる
4.2.2 オーケストレーションベースのサーガ
ビジネスロジックを管理するサービスを中心にして、メッセージのやりとりを行う。参加している他のサービスは共通の1つのチャネルで返信を行うことで、関心を減らすことができる。ビジネスロジックを集約できるが、難しくなりすぎないように複数のサービスにまたがるステートを管理する部分のみに集中させることが重要。
4.3 分離性の欠如への対処方法
サーガを使うと、ACIDのうち分離性が失われてしまう。そのため、更新の消失や、ダーティリード、反復不能読み取りなどが発生してしまう。
4.3.2 分離性の欠如に対処するためのカウンターメジャー
- Semantic lock: アプリケーションレベルのロック。stateに
*_PENDING
のようなものを追加して、その場合にアプリケーション側で適切に処理を行う。 - Commutative updates: どのように順序でも実行できるように更新処理を設計すること。例えば、決済を単純にキャンセルするのではなく、補償となる逆の処理を行う。
- Pessimistic view: ビジネスリスクが最小になるようにサーガの順序を並べ直すこと。例えば、ダーティリードが起こりそうなサービスの処理を最後に行う。
- Reread value: データを読み直して変更されていないことを確認してから上書きするようにしてダーティライトを防ぐこと
- Version file: レコードに対する更新を記録し、順序を変えられるようにすること
- By value: 個々のリクエストのビジネスリスクによりダイナミックに変更処理メカニズムを選択すること。例えば、決済額が大きいときは厳密な分散トランザクションを利用して、少額の際は以上で紹介したカウンターメジャーを使うなど。
6つぐらい紹介されてるので、まとめる
Chapter 5 マイクロサービスアーキテクチャにおけるビジネスロジックの設計
スナップショットを作って、効率化する versionを条件で絞って、楽観的なロックを実装 イベント形式は削除の操作が大変
Chapter 7 マイクロサービスアーキテクチャでのクエリーの実装
複数のサービスにまたがったデータを一度に取得するには大きく2種類方法がある。
一つはAPI compositionパターンで、BFFなどで複数のサービスを呼び出してアプリケーションで結合して返す方法。サービス間でデータの独立性が保てなかったり、可用性が下がるなどのデメリットはあるが、シンプルなものはこれで概ね対応できる。
もう一つはCQRS(Command Query Responsibility Segregation)パターンで、この方法では更新CUDの操作と読み込みRの操作を別々のサービスで行う。Commandのサービスがイベントをpublishして、それをQueryのサービスがsubscribeして独自のDBに保存する。Queryに最適なDBを選択できたり、関心を分離でき、複雑なQueryも扱えるようになる。一方で、同時更新やイベントを冪等に処理したり、レプリケーションの遅延を考慮した上で実装を行う必要がある。
Chapter 8 外部APIパターン
8.2 APIゲートウェイの実装
8.2.1 API Gateway パターンの概要
- リクエストのルーティング
- API合成: 複数のサービスのリクエストの結果を処理する
- エッジ機能: 認証、認可、流量制限、キャッシュ、メトリクスの収集、ログ
- プロトコル変換: 外部との通信に最適なプロトコルと、内部通信に最適なプロトコルを変換する
Chapter 11 本番環境に耐えられるサービスの開発
- 認証・認可
- 環境変数などによるconfigの設定
- Observability
- Health check
- Log集約
- 分散トレーシング
- アプリケーションのメトリクス
- Exceptionトラッキング
- Audit logging
- サービスメッシュ
次のアクション
マイクロサービスパターン[実践的システムデザインのためのコード解説] (impress top gear)
- 作者:Chris Richardson
- 発売日: 2020/03/23
- メディア: 単行本(ソフトカバー)
「リレーショナルデータベース入門」を読んだ
リレーショナルデータベース入門―データモデル・SQL・管理システム・NoSQL (Information & Computing)
- 作者:良文, 増永
- 発売日: 2017/03/01
- メディア: 単行本
全体の感想
DBの本格的な入門書としては、今まで読んだ中では一番良かったように感じた。ただ個人的には、出会うタイミングには恵まれなかった。
最初の方は集合論から始まり、データを効率よく操作したり、表現するにはどのようなものでなければならないかについて言及されていた。中盤からはRDBの種類に関係なく一般的なDBMSがどのような仕組みなのか、クエリーを投げてからデータの読み込み、書き込みが行われるまでにどのような処理が行われているのかについて説明されていた。クエリの処理に必要なコストの計算方法なども紹介されており、joinやindexが内部でどのように処理されるのかも説明されていた。
クエリーを投げてから、内部でどのように処理が行われているのか理解できていない人にはオススメ。
目次
- 全体の感想
- 目次
- 概要
- 第2章 リレーショナルデータモデル
- 第3章 リレーショナルデータベースのデータ操作言語
- 第6章 データベース管理システムの標準アーキテクチャと機能
- 第8章 ファイル編成とアクセス法
- 第9章 リレーショナルDBMSの質問処理とその最適化
- 第11章 トランザクションの同時実行制御
- 第12章 分散型データベース管理システム
概要
第2章 リレーショナルデータモデル
2.1 本章のはじめに
データモデルは次の3要素から成り立っている。
- 構造記述
- 意味記述
- 操作記述
2.9 空
「Interim Report ANSI/X3/SPARC Study Group on Data Base Management Systems」によると14種類の意味がある。
- Not valid for this individual
- Valid, but does not yet exist for this individual
- Exists, but not permitted to be logically stored
- Exists, but not knowable for this individual
- Exists, but not yet logically stored for this individual
- Logically stored, but subsequently logically deleted
- Logically stored, but not yet available
- Available, but understanding change (may be no longer valid)
- Available, but of suspect validity (unreliable)
- Available, but invalid
- Secured for this class of conceptual data
- Secured for this individual object
- Secured at this time
- Derived from null conceptual data (any of the above)
第3章 リレーショナルデータベースのデータ操作言語
3.3 リレーショナル代数
- 集合演算
- 和集合演算
- 差集合演算
- 共通集合演算
- 直積演算
- リレーショナル代数に特有の演算
- 射影演算
- 属性を選択できる
- 選択演算
- 比較可能な任意の2つの属性の条件から、行を選択できる
- 定数との比較は、定数の属性を追加して、それと比較することで実現される
- 結合演算
- ざっくり言うと、joinのこと
- 商演算
- 例えば、供給元と部品のリレーションから、ある部品の集合を供給する供給元のリレーションを生成するなど
- 射影演算
3.5 リレーショナル代数演算の拡張
選択演算を満たすために、比較可能である必要がある。そこで、nullを導入するためには3値論理を導入する必要がある。
3.7 リレーショナル代数とリレーショナル論理の等価性
リレーショナル代数とリレーショナル論理は等価であると知られている。
いずれかを満たす時、リレーショナル完備という。
第6章 データベース管理システムの標準アーキテクチャと機能
6.3 ANSI/X3/SPARCのDBMSの3層スキーマ構造
6.4 3層スキーマの意義
物理的データ独立性と論理的データ独立性を担保できる。
6.5 DBMSの3大機能
6.6 リレーショナルDBMSのメタデータ管理
メタデータ自身もテーブルのスキーマとして定義して管理されている。
第8章 ファイル編成とアクセス法
8.4 ファイル編成とアクセス法
8.4.1 ファイル編成
- ヒープ編成
- 2次記憶に書き込まれた時刻順にレコードを順番付ける
- 大量の挿入を高速に行える
- 線形探索なので、レコードが多いと時間がかかる。また、虫食いブロックが多発して使用効率が悪くなってしまうので、記憶管理が必要になる
- 順次編成
- キーの順番で連続するように保存する
- 高速にアクセスできる
- レコードの削除、更新などの度にソートし直さなければならない
- 直接編成
- hash編成ともいい、キーをハッシュ関数に施して位置を決定する
- 高速にアクセスできる
- rangeクエリーが非効率
8.4.2 アクセス法
- 順次アクセス法
- インデックス法
- ハッシュ法
8.6 インデックス法
8.6.1 インデックスとは何か
順序フィールド上にインデックスを張る場合、以下の2種類が定義できる。
- 密集インデックス: すべての順序フィールド値が出現する
- 点在インデックス: 一部のみ。アクセスする際は、近い値にアクセスしてから走査してたどり着く。データ量は小さくてすむが、パフォーマンスは良くない。
8.6.2 ISAM
インデックス付順次アクセス法(Indexed Sequential Access Method)
8.7 B+木
多段インデックスで、葉ノードは次のキーのポインタも保持しているので、range queryにも対応できる。平衡を保とうとするので、挿入、削除のコストは低くない。
第9章 リレーショナルDBMSの質問処理とその最適化
9.3 質問処理とそのコスト
9.3.1 コスト式
C = Ci/o + ω x Ccpu
- C: 質問を処理するコスト
- Ci/o: 質問を処理するためにフェッチしたページの総数。さらにインデックスとデータに分解できる。
- Ccpu: 質問を処理するために費やしたCPU使用率
- ω: CPU使用率をページ数に換算するための重み係数
9.3.3 結合質問処理とそのコスト式
- 入れ子型ループ結合法とコスト式
# R: アウタ表, S: インナ表 for each t in R for each t` in S such that t[B] t`[B] compute t * t` end
- ソートマージ結合法とコスト式
- 結合列で2つのテーブルのレコードをソートして、そこから順次に先頭から一致するものを結合させる。
- 一般には、入れ子型ループ結合法よりも高速に処理が行える。
- ソートした結果、インナ表Sが順次編成ファイルとなるので、高速に読み出せるから
- ハッシュ結合とコストの計算式
- 小さい方のテーブルをハッシュに変換して、そのハッシュに大きい方のテーブルで順次に参照する
第11章 トランザクションの同時実行制御
11.5 多版同時実行制御(MVCC)
ある値を複数のバージョンを保持して、トランザクションに応じたtimestampのバージョンを返す
11.6 SQLの隔離性水準
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
第12章 分散型データベース管理システム
12.4 分散型質問処理
12.4.3 準結合演算
異なるサイトにあるテーブルを結合するとき、結合に必要な属性だけ先に渡し、最終的に必要な行の他の属性を再度取得する。こうすることで、データの転送量を減らすことができる。
リレーショナルデータベース入門―データモデル・SQL・管理システム・NoSQL (Information & Computing)
- 作者:良文, 増永
- 発売日: 2017/03/01
- メディア: 単行本
「JavaScript Primer」を読んだ
目的、モチベーション
最近はバックエンドしか触ってなくて、久しぶりにフロントエンドのキャッチアップしておきたかったから。
全体の感想
最近のJavaScriptを知らない人や、雰囲気で触っている人には良いと思った。
本がGitHubで管理されていて差分も把握しやすいため、新しいバージョンが公開されたときにも部分的にキャッチアップしやすくて良いと感じた。
これが無料で読めるのは本当にありがたいなと思った。
目次
- 目的、モチベーション
- 全体の感想
- 目次
- メモ
メモ
第一部: 基本文法
変数と宣言
varの使い方はletとほとんど同じで、varキーワードには同じ名前の変数を再定義できてしまう問題があります。
データ型とリテラル
BigInt
数値リテラルは倍精度浮動小数(64bit)で数値を扱うのに対して、BigIntでは任意の精度の整数を扱えます
console.log(1n); // => 1n // 2^53-1より大きな値も扱える console.log(9007199254740992n); // => 9007199254740992n // BigIntは整数を扱うデータ型であるため、次のように小数点を含めた場合は構文エラーとなります。 1.2n; // => SyntaxError
文字列(String)
"
(ダブルクォート)と '
(シングルクォート)はまったく同じ意味
テンプレートリテラル
`(バッククォート)で囲んだ範囲を文字列とするリテラルです。 テンプレートリテラルでは、複数行の文字列を改行記号のエスケープシーケンス(\n)を使わずにそのまま書くことができます。
`複数行の
文字列を
入れたい`; // => "複数行の\n文字列を\n入れたい"
[コラム] undefinedはリテラルではない
ただのグローバル変数
演算子
比較演算子
厳密等価演算子(===)
// 同じ型で同じ値である場合に、trueを返す console.log(1 === 1); // => true console.log(1 === "1"); // => false // オブジェクトの場合は、同じ参照のときtrueを返す const objA = {}; const objB = {}; console.log(objA === objB); // => false console.log(objA === objA); // => true
等価演算子(==)
console.log(1 == 1); // => true // オブジェクトは参照が一致しているならtrueを返す const objA = {}; const objB = {}; console.log(objA == objB); // => false console.log(objA == objA); // => true // オペランド同士が異なる型の値であった場合に、 同じ型となるように暗黙的な型変換をしてから比較する // 文字列を数値に変換してから比較 console.log(1 == "1"); // => true // "01"を数値にすると`1`となる console.log(1 == "01"); // => true // 真偽値を数値に変換してから比較 console.log(0 == false); // => true // nullの比較はfalseを返す console.log(0 == null); // => false // nullとundefinedの比較は常にtrueを返す console.log(null == undefined); // => true // null または undefined以外は厳密等価演算子を使うべき const value = undefined; /* または null */ // === では2つの値と比較しないといけない if (value === null || value === undefined) { console.log("valueがnullまたはundefinedである場合の処理"); } // == では null と比較するだけでよい if (value == null) { console.log("valueがnullまたはundefinedである場合の処理"); }
論理演算子
falsyな値とは次の7種類の値のこと
false
undefined
null
0
0n
NaN
""
(空文字列)
nulishとは、評価結果がnullまたはundefinedとなる値のこと
暗黙的な型変換
さまざまな暗黙的な型変換
1 + "2"; // => "12" 1 - "2"; // => -1
関数と宣言
可変長引数
// [ES2015] Rest parameters function fn(...args) { // argsは引数の値が順番に入った配列 console.log(args); // => ["a", "b", "c"] } fn("a", "b", "c"); // Spread構文 function fn(x, y, z) { console.log(x); // => 1 console.log(y); // => 2 console.log(z); // => 3 } const array = [1, 2, 3]; fn(...array); // arguments function fn() { // `arguments`はインデックスを指定して各要素にアクセスできる // Array ライクなだけで、Arrayのメソッドは使えない console.log(arguments[0]); // => "a" console.log(arguments[1]); // => "b" console.log(arguments[2]); // => "c" } fn("a", "b", "c");
[ES2015] 関数の引数と分割代入
// 第1引数のオブジェクトから`id`プロパティを変数`id`として定義する function printUserId({ id }) { console.log(id); // => 42 } const user = { id: 42 }; printUserId(user);
関数式
[ES2015] Arrow Function
// 仮引数の数と定義 const fnA = () => { /* 仮引数がないとき */ }; const fnB = (x) => { /* 仮引数が1つのみのとき */ }; const fnC = x => { /* 仮引数が1つのみのときは()を省略可能 */ }; const fnD = (x, y) => { /* 仮引数が複数のとき */ }; // 値の返し方 // 次の2つの定義は同じ意味となる const mulA = x => { return x * x; }; // ブロックの中でreturn const mulB = x => x * x; // 1行のみの場合はreturnとブロックを省略できる
次のような特徴がある。
- 名前をつけることができない(常に匿名関数)
this
が静的に決定できる(詳細は「関数とスコープ」の章で解説します)function
キーワードに比べて短く書くことができるnew
できない(コンストラクタ関数ではない)arguments
変数を参照できない
条件分岐
switch文
breakが必要で、厳密等価演算子で評価される。
ループと反復処理
for...in文
事故りやすいので、基本的には使わないこと。
オブジェクト
[ES2015] オブジェクトと分割代入
const languages = { ja: "日本語", en: "英語" }; const { ja, en } = languages; console.log(ja); // => "日本語" console.log(en); // => "英語"
プロパティの削除
delete
を使う。
const obj = { key1: "value1", key2: "value2" }; delete obj.key1; // key1プロパティが削除されている console.log(obj); // => { "key2": "value2" }
[コラム] constで定義したオブジェクトは変更可能
Objectを変更不可能にするには、 Object.freeze
を使う。
"use strict"; const object = Object.freeze({ key: "value" }); object.key = "value"; // => TypeError: "key" is read-only
プロパティの存在を確認する
定義されていない場合は、 undefined
が返るので以下の様に確認する。
const obj = { key: undefined }; if ("key" in obj) { console.log("`key`プロパティは存在する"); } if (obj.hasOwnProperty("key")) { console.log("`obj`は`key`プロパティを持っている"); }
[ES2020] Optional chaining演算子(?.)
const title = widget?.window?.title ?? "未定義"; console.log(`ウィジェットのタイトルは${title}です`);
オブジェクトの静的メソッド
const obj = { "one": 1, "two": 2, "three": 3 }; console.log(Object.keys(obj)); // => ["one", "two", "three"] console.log(Object.values(obj)); // => [1, 2, 3] console.log(Object.entries(obj)); // => [["one", 1], ["two", 2], ["three", 3]]
複製するときは、 Object.assign({}, obj)
で実現できるが、shallowCopyなので注意。
プロトタイプオブジェクト
in演算子とObject#hasOwnPropertyメソッドの違い
const obj = {}; // `obj`というオブジェクト自体に`toString`メソッドが定義されているわけではない console.log(obj.hasOwnProperty("toString")); // => false // `in`演算子は指定されたプロパティ名が見つかるまで親をたどるため、`Object.prototype`まで見にいく console.log("toString" in obj); // => true
オブジェクトの継承元を明示するObject.createメソッド
// const obj = {} と同じ意味 const obj = Object.create(Object.prototype); // `obj`は`Object.prototype`を継承している console.log(obj.hasOwnProperty === Object.prototype.hasOwnProperty); // => true // prototypeを継承しないオブジェクト const nullObj = Object.create(null);
配列
オブジェクトが配列かどうかを判定する
Array.isArray
を使う。
const obj = {}; const array = []; console.log(Array.isArray(obj)); // => false console.log(Array.isArray(array)); // => true console.log(typeof array); // => "object"
[コラム] undefinedの要素と未定義の要素の違い
const denseArray = [1, undefined, 3]; const sparseArray = [1, , 3]; console.log(denseArray[1]); // => undefined console.log(sparseArray[1]); // => undefined console.log(denseArray.hasOwnProperty(1)); // => true console.log(sparseArray.hasOwnProperty(1)); // => false
配列から要素を削除
const array = ["a", "b", "c"]; // 1番目から1つの要素("b")を削除 array.splice(1, 1); console.log(array); // => ["a", "c"]
const array = [1, 2, 3]; array.length = 0; // 配列を空にする console.log(array); // => []
[コラム] Array-likeオブジェクト
Array.from
メソッドでArrayに変換できる。
文字列
正規表現オブジェクト
- 正規表現リテラル: ソースコードをロード(パース)した段階で正規表現のパターンが評価される
- RegExpコンストラクタ: 通常の関数と同じように実際にRegExpコンストラクタを呼び出すまでパターンは評価されない
関数とthis
実行コンテキストとthis
<script> // 実行コンテキストは"Script" console.log(this); // => window </script> <script type="module"> // 実行コンテキストは"Module" console.log(this); // => undefined </script>
// ブラウザでは`window`オブジェクト、Node.jsでは`global`オブジェクトを参照する
console.log(globalThis);
非同期処理:コールバック/Promise/Async Function
非同期処理はメインスレッドで実行される
並列処理ではなく、並行処理。
[ES2015] Promise
Promise.all
で複数のPromiseをまとめることができるPromise.race
でもまとめることができるが、最初の結果だけが反映される。
[ES2015] Map/Set
WeakMap
const map = new WeakMap(); // キーとなるオブジェクト let obj = {}; // {} への参照をキーに値をセットする map.set(obj, "value"); // {} への参照を破棄する obj = null; // GCが発生するタイミングでWeakMapから値が破棄される
[コラム] キーの等価性とNaN
const map = new Map(); map.set(NaN, "value"); // NaNは===で比較した場合は常にfalse console.log(NaN === NaN); // => false // MapはNaN同士を比較できる console.log(map.has(NaN)); // => true console.log(map.get(NaN)); // => "value"
JSON
オブジェクトをJSON文字列に変換する
const obj = { id: 1, name: "js-primer", bio: null }; const replacer1 = (key, value) => { if (value === null) { return undefined; } return value; }; console.log(JSON.stringify(obj, replacer1)); // => '{"id":1,"name":"js-primer"}' const replacer2 = ["id"]; console.log(JSON.stringify(obj, replacer2)); // => '{"id":1}' // スペース2個でインデントされたJSON console.log(JSON.stringify(obj, null, 2)); /* { "id": 1, "name": "js-primer", "bio": null } */
JSONにシリアライズできないオブジェクト
シリアライズ前の値 | シリアライズ後の値 |
文字列・数値・真偽値 | 対応する値 |
null | null |
配列 | 配列 |
オブジェクト | オブジェクト |
関数 | 変換されない(配列のときはnull) |
undefined | 変換されない(配列のときはnull) |
Symbol | 変換されない(配列のときはnull) |
RegExp | {} |
Map, Set | {} |
toJSON
メソッドを使ったシリアライズ
オブジェクトがtoJSONメソッドを持っている場合、JSON.stringifyメソッドはtoJSONメソッドの返り値を使う。
const obj = { foo: "foo", toJSON() { return "bar"; } }; console.log(JSON.stringify(obj)); // => '"bar"'
「実践 Rustプログラミング入門」を読んだ
全体の感想
他の言語を普段使っていてRustに触れたことがない人にとっては、良い本だと思った。
3部からなり、第1部では基礎的な文法から、Rust特有の考え方(GCの所有権、メモリ安全性、スレッド安全性など)の説明があった。第2部では、実践的なアプリケーションを実装して、コマンドラインツールやWebアプリケーション、GUI、組み込みなど幅広く扱われていた。第3部では、応用的なtipsが紹介されていて、他の言語との連携や、コミュニティ、リリースサイクルなどについて言及されていた。
Rust言語自体は、パフォーマンスがよく、型推論も強くサポートされ、モダンな言語にある機能はほとんど網羅されており、コミュニティも活発で幅広いアプリケーションやツールも作れそうで、いろんな企業が導入し始めてるのも納得できた。特にスレッド安全性などについては、言語の通りに実装すれば未然にバグも防げそうに感じたので、強力だなと思った。
一部パフォーマンスが求められる部分だけRustで書くことも結構簡単にできそうなので、部分的に導入するのも良さそうだと思った。
目次
- 全体の感想
- 目次
- 概要
- 次のアクション
概要
Part 1 入門
Chapter1 プログラミング言語 Rust
1-2 とにかく実行速度が速い
- Rust は機械語に直接コンパイルされる
- ガベージコレクションをもたない
- 「所有権」「借用」「ライフタイム」という新しい仕組みで、言語側で管理する
- 「ゼロコスト抽象化」を追求している
- 抽象化するときにオーバーヘッドとかが生じないようになっている。
1-4 OS から Web アプリケーションまで幅広く実装できる
Chapter3 Rustの基本
3-1 基本的な文法
- NaNは
==
を満たさないので、f32ではPartialEq
やPartialOrd
を使う
3-2 Rustを支える言語機能
所有権と借用
- メモリ管理のために、所有できるのは1つのオブジェクトだけ
- 参照は複数から行えるので、参照渡しをする
- 可変の場合は、一度に一つだけ
- 不変の場合は、制限なし
- デストラクタとして
Drop
トレイトが用意されている
スレッド安全性
マルチスレッドの例
use std::thread; fn main() { let mut handlers = Vec::new(); for x in 0..10 { // moveキーワードで所有権をスレッドに移す handlers.push(thread::spawn(move || { println!("Hello, world!: {}", x); })); } for handle in handles { // スレッドの処理が終了するのを待つ let _ = handle.join(); } }
共有メモリ
use std::rc::{Arc, Mutex}; use std::thread; fn main() { let mut handlers = Vec::new(); let data = Arc::new(Mutex::new(vec![1; 10])); for x in 0..10 { // リファレンスカウンターを増やす let data_ref = data.clone(); handlers.push(thread::spawn(move || { // 可変参照を得る let mut data = data_ref.lock().unwrap(); data_ref[x] += 1; })); } for handle in handles { // スレッドの処理が終了するのを待つ let _ = handle.join(); } dbg!(data); }
メッセージパッシング
use std::sync::mpsc; use std::thread; fn main() { let mut handles = Vec::new(); let mut data = vec![1; 10]; let mut snd_channels = Vec::new(); let mut rcv_channels = Vec::new(); for _ in 0..10 { let (snd_tx, snd_rx) = mpsc::channel(); let (rcv_tx, rcv_rx) = mpsc::channel(); snd_channels.push(snd_tx); rcv_channels.push(rcv_tx); handlers.push(thread::spawn(move || { let mut data = snd_rx.recv().unwrap(); data += 1; let _ = rcv_tx.send(data); })); } for x in 0..10 { let _ = snd_channels[x].send(data[x]); } for x in 0..10 { data[x] = rcv_channels[x].recv().unwrap(); } for handle in handlers { let _ = handle.join(); } dbg!(data) }
Part 2 実践
ソースコードはこちら。
Chapter4 プログラムを作成する
Chapter5 Webアプリケーションの開発
Chapter6 WebAssembly
6-4 サンプルプログラム:ナンバープレースを解く
数独を解く。
JSと比較したベンチマークの結果では、JITコンパイラの最適化の影響のためか、必ずしもRustの方が速いとは限らないみたいだった。
Chapter7 GUIアプリケーション
IcedというGUIのフレームワークのようなライブラリを使って、タイマーのアプリケーションを作成した。
7-1 RustにおけるGUIの現状
GUIクレートの紹介
Chapter8 組み込みシステム
エミュレータを使って、LEDをチカチカさせるものの実装の仕方が紹介されていた。
Chapter9 開発ツール
9-2 フォーマッタ・リンター
9-3 コードカバレッジ
- cargo-tarpaulin: テストのコードカバレッジを測定できる。実行されてない行数も特定できる。
9-4 ベンチマーク・プロファイラ
- ベンチマーク
- プロファイラ
- cargo-profiler
- flamegraph: 可視化してくれる
Chapter10 プロダクトをリリースする
10-2 ビルドの再現性
- Cargo.lock: 依存クレートのバージョン
- rust-toolchain: コンパイラのバージョン
10-3 バイナリサイズの最適化
最適化のオプション
[prifile.release] // Link Time Optimization lto = true // 0: 最適化なし // 1: 基本的な最適化 // 2: 追加の最適化 // 3: すべての最適化(リリースビルドでのデフォルト) // "s": バイナリサイズの最適化 // "z": バイナリサイズの最適化(ループのベクトル化を行わない) opt-level = "z" // コンパイル時の並列度を下げることで、タスクをまたいだ最適化を行える codegen-units = 1 // panic時、スタックを巻き戻してバックトレースを生成する必要がないとき panic = "abort" // シンボル情報の削除 $ strip ./target/release/hello
10-6 ファジング
cargo-fuzzを使える。
Part 3 Tips
Chapter11 いろいろなRustの発展的Tips
11-2 FFIによる他言語との連携
FFI(Foreign Function Interface)によって、他言語と簡単に連携できる。
11-4 unsafe
Rustはメモリ安全性を保証しているが、実装者の責任によってその安全性を無視して実装することができる。
- 極力使わないこと
- 使う場合は、局所的に使うこと
UB (未定義動作)
アプリケーションがライブラリやコンパイラの規約を守らなかったために、どのような動作結果も保証されない状態。
参考資料
11-5 Rust のエディション
Rustでは通常のリリースは6週間ごとに行われている。
大きな変更に関しては、約3年毎にエディションという単位でリリースされる。エディションは互換性があり、クレートごとに指定できる。
11-6 Rust製のOSS
特にすぐ使える有名なもの
11-7 Rust のコミュニティ
最新情報を得るために
次のアクション
もう少し、バックエンドで本番運用できそうか、試してみる。