20201130のMySQLに関する記事は10件です。

MySQLのEXPLAINをかいつまんで見る

概要

MySQLのEXPLAINを読むと良いぞ。
というか読まなきゃダメな時がいつかくる。

各項目の説明についてはこの記事がよくまとまってる。(参考にさせていただきました)
MySQL EXPLAINのそれぞれの項目についての覚書

なんでEXPLAINが大事なの?

EXPLAINは「実行計画」を表示してくれる。どういう順序で、どういう方法でデータを取ってくるか表示してくれるので、クエリのパフォーマンスをチェックすることができる。
スロークエリが発生していたら必ずEXPLAINする癖をつけよう。発生していなくても、自信がなかったらEXPLAINしよう。

RDBMSによって出し方や出力が異なるので注意。
今回はMySQLの話。

そもそも遅くなる要員(よく見かけるやつら)

EXPLAINを見る前に、SQLがなぜ遅くなるのか把握してきたい。

インデックスを使っていない・設定していない

言わずもがな。フルテーブルスキャンって感じ。
O(n)の計算量。インデックスを使えばO(logn)。

相関サブクエリ

二重ループが走ることになる。外側のクエリでヒットしたデータ一件ずつに対してサブクエリが実行される。
O(nm)の計算量のイメージ。

filesort

ソートにクイックソートを使う。O(nlogn)。
インデックスを使ったソートの方が圧倒的に早い(というかインデックスつきのカラムは実質ソートされた状態でデータが保存されている)。

temporary

一時テーブルを使ってソートする。filesortはオンメモリで行うが、データ量が大きくなった場合は一時テーブルを作って、そこからselectする形をとる。
I/Oが重くなるだけでなくテーブルを一回作って書き込むわけだからそりゃ重い。

インデックスが有効活用できない

実質O(n)の計算量。フルインデックススキャンとかその辺。結局O(n)になる
例えば10000件データが入っているテーブルを検索して9999件ヒットする絞り込み条件を選択した場合、インデックスが設定されているカラムで条件を指定していたとしてもほとんど意味がない。

見るべきところ

上で述べたことが、EXPLAINのどこに現れるのか

select_type

DEPENDENT SUBQUERY に注意。相関サブクエリを意味する。
外側のクエリで十分絞り込めていれば遅くはならない。

type

indexALL に注意。
フルインデックススキャン、フルテーブルスキャンになり、インデックスが有効活用できていない。

key

null に注意。
インデックスが使われていないことを意味する。
ここが null の場合は possible_keys を見ると良い。利用可能なインデックスを探す。
ただしインデックスが設定してあっても利用されないことがあるので注意。

rows

数字が大きいと注意
テーブルから取得する予想件数が多いことになるので、単純に n が大きくなる。I/Oも辛くなる。
特に DEPENDENT SUBQUERYfilesorttemporary との合わせ技が発揮されると一瞬で死ぬ。

filteredの数字が小さいとまだ救われる(filteredは絞り込まれる割合を百分率で表す)

extra

Using filesortUsing temporary に注意。

まとめ

EXPLAINの結果をしっかり見て、ボトルネックを探そう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

閉包表で子孫から祖先を参照

概要

組織図のようなツリー構造のデータがあったときに、子孫から祖先を参照するのに閉包表という方法を知ったので、それの備忘録

今回のケース

所属1 (1)
┠ グループ1 (2)
┃   ┠ チーム1 (5)
┃     ┠ チーム2 (6)
┃
┝ グループ2 (3)
┃     ┠ チーム3 (7) 
┃
┝ グループ3 (4)

みたいな組織図があった時に子孫から祖先を参照して、データを取得したい

テーブルとデータ作成

テーブル

CREATE TABLE IF NOT EXISTS departments (
    id INTEGER NOT NULL UNIQUE PRIMARY KEY,
    name TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS department_paths (
    ancestor INTEGER NOT NULL REFERENCES entries (id),
    descendant INTEGER NOT NULL REFERENCES entries (id),
    depth INTEGER NOT NULL CHECK (depth >= 0),
    PRIMARY KEY (descendant ASC, ancestor ASC)
);

データ

INSERT INTO departments (id, name) VALUES (1, 'Department 1');
INSERT INTO department_paths (descendant, ancestor, depth) VALUES (1, 1, 0);

INSERT INTO departments (id, name) VALUES (2, 'Group 1');
INSERT INTO department_paths (descendant, ancestor, depth) VALUES (2, 1, 1);
INSERT INTO department_paths (descendant, ancestor, depth) VALUES (2, 2, 0);

INSERT INTO departments (id, name) VALUES (3, 'Group 2');
INSERT INTO department_paths (descendant, ancestor, depth) VALUES (3, 1, 1);
INSERT INTO department_paths (descendant, ancestor, depth) VALUES (3, 3, 0);

INSERT INTO departments (id, name) VALUES (4, 'Group 3');
INSERT INTO department_paths (descendant, ancestor, depth) VALUES (4, 1, 1);
INSERT INTO department_paths (descendant, ancestor, depth) VALUES (4, 4, 0);

INSERT INTO departments (id, name) VALUES (5, 'Team 1');
INSERT INTO department_paths (descendant, ancestor, depth) VALUES (5, 1, 2);
INSERT INTO department_paths (descendant, ancestor, depth) VALUES (5, 2, 1);
INSERT INTO department_paths (descendant, ancestor, depth) VALUES (5, 5, 0);

INSERT INTO departments (id, name) VALUES (6, 'Team 2');
INSERT INTO department_paths (descendant, ancestor, depth) VALUES (6, 1, 2);
INSERT INTO department_paths (descendant, ancestor, depth) VALUES (6, 2, 1);
INSERT INTO department_paths (descendant, ancestor, depth) VALUES (6, 6, 0);

INSERT INTO departments (id, name) VALUES (7, 'Team 3');
INSERT INTO department_paths (descendant, ancestor, depth) VALUES (7, 1, 2);
INSERT INTO department_paths (descendant, ancestor, depth) VALUES (7, 3, 1);
INSERT INTO department_paths (descendant, ancestor, depth) VALUES (7, 7, 0);

データの取得

各departmentsから見た階層の深さと経路

SELECT 
    id,
    name,
    MAX(depth) AS depth,
    GROUP_CONCAT(ancestor) AS path
FROM (
    /* 各departmentsから見た親子関係をリストアップ */
    SELECT 
        id, 
        name, 
        descendant, 
        ancestor, 
        depth
    FROM departments
    JOIN department_paths ON descendant = id
    ORDER BY depth DESC
) dept_deptpath
GROUP BY descendant
ORDER BY path;

結果

id name depth path
1 Department 1 0 1
2 Group 1 1 1,2
5 Team 1 2 1,2,5
6 Team 2 2 1,2,6
3 Group 2 1 1,3
7 Team 3 3 1,3,7
4 Group 3 1 1,4

経路は取れたけど、1行に祖先、親、子供にデータを取りたい

親子関係を行にまとめて取得

    SELECT
        dept.id,
        dept.name as child_department,
        parent.name as parent_department,
        grand_parent.name as grand_parent_department,
        MAX(depth) as depth
    FROM departments dept
    LEFT OUTER JOIN departments AS parent
        ON parent.id = (
            SELECT ancestor
            FROM department_paths
            WHERE descendant = dept.id AND depth = 1)
    LEFT OUTER JOIN departments AS grand_parent
        ON grand_parent.id = (
            SELECT ancestor
            FROM department_paths
            WHERE descendant = dept.id AND depth = 2)
    JOIN department_paths deptpath ON deptpath.descendant = dept.id
    GROUP BY dept.id, parent.id, grand_parent.id

結果

id child parent grand_parent depth
1 Department 1 NULL NULL 0
2 Group 1 Department 1 NULL 1
3 Group 2 Department 1 NULL 1
4 Group 3 Department 1 NULL 1
5 Team 1 Group 1 Department 1 2
6 Team 2 Group 1 Department 1 2
7 Team 3 Group 2 Department 1 2

仕様で三階層まで取れればいいので、今回はこれにしたけれど、深さに左右されない書き方があれば知りたい

おまけ: 子孫から見た祖先だけ取りたい

SELECT
    descendant,
    dept.name as department
FROM department_paths
JOIN departments dept ON dept.id = ancestor
/* 祖先は子孫としては1レコードしかないことを利用 */
WHERE ancestor IN (
    SELECT id
    FROM departments
    WHERE 1 = (
        SELECT COUNT(*) FROM department_paths WHERE descendant = id
    )
)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

not existsを扱う

以下のテーブルがある。

select * from testscores;

+------------+---------+-------+
| student_id | subject | score |
+------------+---------+-------+
|        100 | 国語    |    80 |
|        100 | 理科    |    80 |
|        100 | 算数    |   100 |
|        200 | 国語    |    95 |
|        200 | 算数    |    80 |
|        300 | 国語    |    90 |
|        300 | 社会    |    55 |
|        300 | 算数    |    40 |
|        400 | 算数    |    80 |
+------------+---------+-------+

全ての教科で50点以上取っている生徒を取得する。

select
distinct student_id
from
testscores ts1
where
not exists
(
    select
    *
    from
    testscores ts2
    where
    ts1.student_id = ts2.student_id
    and ts2.score < 50
)

+------------+
| student_id |
+------------+
|        100 |
|        200 |
|        400 |
+------------+

次は算数が80点以上、国語が50点以上の生徒を取得する。

select
distinct student_id
from
testscores ts1
where
subject in('算数','国語')
and not exists
(
    select
    *
    from
    testscores ts2
    where
    ts1.student_id = ts2.student_id
    and 1 =
    case when subject = '算数' and score < 80 then 1
    when subject = '国語' and score < 50 then 1
    else 0 end
)

+------------+
| student_id |
+------------+
|        100 |
|        200 |
|        400 |
+------------+

上記のSQLでは、国語のデータが存在しないstudent_id400の生徒が取得されてしまっている。
それを除外したい場合、行数を数えるhaving句を追加すればいい。

select
student_id
from
testscores ts1
where
subject in('算数','国語')
and not exists
(
    select
    *
    from
    testscores ts2
    where
    ts1.student_id = ts2.student_id
    and 1 =
    case when subject = '算数' and score < 80 then 1
    when subject = '国語' and score < 50 then 1
    else 0 end
)
group by student_id
having count(*) = 2

+------------+
| student_id |
+------------+
|        100 |
|        200 |
+------------+

こちらを参考にさせていただきました。
達人に学ぶSQL徹底指南書 第2版 初級者で終わりたくないあなたへ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

テーブルに存在しないデータを探す

以下のテーブルがある

select * from meetings;

+-----------+--------+
| meeting   | person |
+-----------+--------+
| 第1回    | 伊藤   |
| 第1回    | 坂東   |
| 第1回    | 水島   |
| 第2回    | 伊藤   |
| 第2回    | 宮田   |
| 第3回    | 坂東   |
| 第3回    | 宮田   |
| 第3回    | 水島   |
+-----------+--------+

meetingに出席しなかったpersonを取得する。

方法として、全員が解禁したと仮定した場合の集合を作り、
そこから現実に出席した人々を引き算する。

全員が解禁した場合の集合はクロス結合で作れる。

select
distinct
m1.meeting
,m2.person
from 
meetings m1 
cross join 
meetings m2

+-----------+--------+
| meeting   | person |
+-----------+--------+
| 第1回    | 伊藤   |
| 第2回    | 伊藤   |
| 第3回    | 伊藤   |
| 第1回    | 坂東   |
| 第2回    | 坂東   |
| 第3回    | 坂東   |
| 第1回    | 水島   |
| 第2回    | 水島   |
| 第3回    | 水島   |
| 第1回    | 宮田   |
| 第2回    | 宮田   |
| 第3回    | 宮田   |
+-----------+--------+

上記から実際の出席者の集合であるmeetingsテーブルに存在しない組み合わせに限定する。

select
distinct
m1.meeting
,m2.person
from 
meetings m1 
cross join 
meetings m2
where
not exists
(
    select
    *
    from
    meetings m3
    where
    m1.meeting = m3.meeting
    and m2.person = m3.person
)
order by
m1.meeting

+-----------+--------+
| meeting   | person |
+-----------+--------+
| 第1回    | 宮田   |
| 第2回    | 坂東   |
| 第2回    | 水島   |
| 第3回    | 伊藤   |
+-----------+--------+

こちらを参考にさせていただきました。
達人に学ぶSQL徹底指南書 第2版 初級者で終わりたくないあなたへ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQL DBとテーブルとカラムの作成方法

目的

  • ターミナルからMySQLにログインしてDB(データベース)とテーブル、テーブル内のカラムを作成する方法をまとめる

実施環境

  • ハードウェア環境
項目 情報
OS macOS Catalina(10.15.5)
ハードウェア MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)
プロセッサ 2 GHz クアッドコアIntel Core i5
メモリ 32 GB 3733 MHz LPDDR4
グラフィックス Intel Iris Plus Graphics 1536 MB
  • ソフトウェア環境
項目 情報 備考
MySQLバージョン 8.0.19 for osx10.13 on x86_64 Homwbrewを用いてこちらの方法で導入→Mac HomebrewでMySQLをインストールする

前提情報

  • 筆者はMacに直接MySQLを導入しターミナルからMySQLにログインして本記事の内容を検証した。
  • MySQLへのログインはrootユーザを使用する。(MySQLのrootユーザのパスワードが分からなくなってしまった方はこちら→[Mac ローカル環境の MySQL 8.x のrootパスワードを忘れた時のリセット方法](https://qiita.com/miriwo/items/1880e9d2ebcfd3c0e60d)
  • 今回作成するDBとテーブル、カラムの詳細を下記に記載する。

    • DB名: create_test
    • テーブル名: users
    • カラム

      カラム名 データ型
      id int
      name varchar(255)

概要

  1. 準備
  2. DBの作成
  3. テーブルとカラムの作成
  4. 確認

詳細

  1. 準備

    1. 下記コマンドを実行してMySQLにログインする。

      $ mysql -u root -p
      
  2. DBの作成

    1. MySQLにログイン後下記SQLを実行してDBを作成する。(create database DB名とするとDBを作成する事ができる。)

      create database create_test;
      
    2. 下記SQLを実行して只今作成したDBを選択する。

      use create_test;
      
  3. テーブルとカラムの作成

    1. 下記SQLを実行してテーブルとカラムを同時に作成する。

      create table users(
          id int, 
          name varchar(255)
      );
      
  4. 確認

    1. 下記SQLを実行して出力されたテーブル名一覧にusersテーブルがあることを確認する。

      show tables;
      
    2. 下記SQLを実行して出力されたusersテーブルのカラムデータが前提情報に記載したものと一致しているか確認する。

      show columns from users;
      
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ローカルでMySQLコンテナに大量INSERTしていたら"unexpected EOF"になった時の話

はじめに

データ削除バッチのベンチマークを取るため、ローカルのMySQLコンテナにデータを800万レコードほどINSERTしていたところ、 packets.go:36: unexpected EOF というエラーに遭遇しました。その時に調べたことや対処したことなどをまとめます。

環境:

  • Go 1.15.2
  • go-sql-driver/mysql v1.4.1
  • MySQL 5.7.30

対処と原因

MaxLifetimeを設定する

packets.go:36: unexpected EOF でぐぐってみると、結構ヒットしたので比較的よくある現象のようです。それらによると、 SetConnMaxLifetime を設定すれば解消するとのことでした。

SetConnMaxLifetime に関してはこちらの記事で説明されています。その名の通り、接続の寿命を設定するもののようです。
Re: Configuring sql.DB for Better Performance

MySQL では wait_timeout という設定で接続がサーバーから切られる恐れがあります。また、OSやルーターが長時間利用されていないTCP接続を切断することもあります。どのケースでも、 go-sql-driver/mysql はクエリを送信した後、レスポンスを受信しようとして初めてTCPが切断されたことを知ります。切断を検知するのに何十秒もかかるかもしれませんし、送信したクエリが実行されたかどうかを知ることもできないので安全なリトライもできません。
こういった危険をなるべく避けるためには、長時間使われていなかった接続を再利用せずに切断し、新しい接続を使うべきです。 SetConnMaxLifetime() は接続の寿命を設定するAPIですが、寿命を10秒に設定しておけば、10秒使われていなかった接続を再利用することもありません。

引数に与える秒数に関しては、

SetConnMaxLifetime() は最大接続数 × 1秒 程度に設定する。多くの環境で1秒に1回接続する程度の負荷は問題にならない。

とあったため、とりあえず1秒を設定しました。

つまり、こんな感じの実装です。

db.SetConnMaxLifetime(time.Second)

これにより、エラーは発生しなくなりました!!

なぜこの設定で問題解消されるのか

MaxLifetime の値はデフォルトでは無制限です。
一見すると、コネクションはできるだけ使いまわした方が再接続のオーバーヘッドも少なく、良いように思われるのですが、接続時間が長すぎることのデメリットも多くあるようです。いくつかの記事でそのことが説明されていますが、例えば、こちらの記事ではこのように説明されていました。

コネクション回数が減る代わりにアイドルのコネクションが必要なくなってもキープし続ける負荷があったり、DB起因でコネクションが中断されたり、フェイルオーバしたりした際に、外部要因で既に切れたコネクションを持ち続けて切断の検知に時間がかかったり、新しいコネクションに切り替えて欲しい際に切り替えがなかなか起こらないというリスクを含んでしまいます

sql.DBのチューニング方法

つまり、今回は「大量INSERT中になにかしらのDBコンテナ側の起因でコネクションが中断された」と推測されます。
DB側でコネクションが中断されても、GoクライアントはFIN通知を受け取るわけではないのでそのコネクションは生きていると判断します。Goクライアントからクエリパケットを送る際に初めてパケット喪失をして気づき、 unexpected EOF のエラーを受け取る格好になります。
このようにならない為に、Goクライアント側で MaxLifeTime の設定をし、Goクライアント側で接続を1秒毎に切っておく対処を追加することで解消されるのかと。

なぜMySQLでエラーが発生したのか

ここまででは、 unexpected EOF が発生する理由と解決方法はわかったのですが、そもそもなぜMySQLでエラーが発生してしまったのかという根本原因が分かっていません。

MySQLコンテナ内のログを確認してみると、 ibuf cursor restoration fails というエラーログがでていることが分かりました。これ自体の意味はよく分かっていないのですが、 MySQL5.7.31でfixされたようです。
https://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-31.html

最終的な解決手段

そこで、当時最新であったMySQL5.7.32にアップデート(ローカルにある5.7のimagreをいったんrmして、再起動)して、 SetConnMaxLifetime の実装を削除して、無事動作することを確認しました。
つまり、バージョン上げるだけで解決しました。

これから

本番環境やステージング環境ではAWSのAuroraを使っており、Auroraでも同様の問題が発生してしまうのかの確認が必要です。
それはこれからの話なので、進展ありましたら、記載します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cloud9 × Rails × Docker × MySQL で環境構築

はじめに

cloud9を使ったDockerの環境構築の記事が少なく戸惑ったこと、自身のアウトプットのために執筆します。

別の記事でも記載していますが、現在転職活動中の身ですのでプロではありません。
間違いなどありましたらご指摘いただければ、自身でも調べ速やかに修正いたします。

参考記事:
丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)

ファイル内容は上記サイトから頂きましたが、port8080に対応するため一部編集を加えています。

目標

タイトルの構成で環境構築をし、「Yay! You’re on Rails!」を表示させる。

環境

Amazon linux
Ruby 2.6.3
Rails 5.2.4.4
MySQL 5.5.62
Docker 19.03.6
Docker-compose 1.27.4

やってきましょう

インスタンスのボリュームを増やす

cloud9を立ち上げてできるインスタンスはサイズが10GBとなっているので、容量が不足する可能性があります。
その都度不要ファイルを削除できればいいのですが、手っ取り早くコンソールから容量を増やしました。
(極少額の課金が発生するかもしれません、料金はご自身で調べてみてください)

まずはcloud9を立ち上げます。
その後EC2の画面に行くと、cloud9と同時に立ち上がったインスタンスがあります。

EC2のサイドメニューから下記の順番で選択します。
1.ボリューム
2.先ほど立ち上がったインスタンスのチェックボックス
3.アクション
4.ボリュームの変更

スクリーンショット 2020-11-29 190704.png

開いたウィンドウのサイズを16GBに変更
スクリーンショット 2020-11-29 190843.png

こうなればOK
スクリーンショット 2020-11-29 190917.png

インスタンスの状態がoptimizing(黄色のマーク)なので、10分程度放置し終わるのを待ちます。
スクリーンショット 2020-11-29 191119.png

状態が緑のマークになると、サイズも16GBとなり反映が確認できます。
スクリーンショット 2020-11-29 193501.png

cloud9のターミナルに移動します。
以下のコマンドで内部からもサイズ変更が反映されているかを確認します。

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        483M   60K  483M   1% /dev
tmpfs           493M     0  493M   0% /dev/shm
/dev/xvda1       16G  8.6G  7.0G  55% /

もしリロードしても反映されていないようなら次のコマンドを入力します。

$ sudo growpart /dev/xvda 1
$ sudo resize2fs /dev/xvda1

Docker-Composeのインストール

インストールの前に このページ で最新のバージョンを確認しておいてください。

下の1.27.4の箇所を確認した最新のバージョンに書き換えてインストールします。
私は"/home/ec2-user/environment"ディレクトリで実行しています。

$ sudo curl -L https://github.com/docker/compose/releases/download/1.27.4/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose 

$ docker-compose コマンドを実行できるように権限を与えます。

$ sudo chmod +x /usr/local/bin/docker-compose

バージョンの確認をして、表示されればインストールは完了です。

$ docker-compose --version
docker-compose version 1.27.4, build 40524192

ファイルの準備

プロジェクトディレクトリを作成して、その中に移動します。

$ mkdir myApp
$ cd myApp

そうしたら必要なファイルをtouchコマンドで作成していきます。
ここでファイルは4つ用意します。

$ touch Dockerfile
$ touch Gemfile
$ touch Gemfile.lock
$ touch docker-compose.yml

通常はviコマンドなどでファイルを書いていきますが、cloud9はせっかくエディタが付いているのでそちらで作業しましょう。

なおGemfile.lockは記載せずに空のままで大丈夫です。

各ファイルの項目は Docker × Ruby on Rails × MySQLの環境構築 この記事を参考にしてください。

Dockerfile
FROM ruby:2.6.3 #適したバージョンを指定
RUN apt-get update -qq && \
    apt-get install -y build-essential \ 
                       libpq-dev \        
                       nodejs           
RUN mkdir /app_name 
ENV APP_ROOT /app_name 
WORKDIR $APP_ROOT
ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock
RUN bundle install
ADD . $APP_ROOT

FROM ruby:2.6.3

cloud9にはあらかじめrubyがインストールされています。
以前同様の環境構築をした際、インストール済みのバージョンとここで指定したバージョンが異なりエラーになりました。(原因は他にあるのかもしれませんが、2つのバージョンが競合したのか?)

その為ここのrubyのバージョンは$ ruby -vで確認したインストール済みのバージョンと同様にしてあります。

Gemfile
source 'https://rubygems.org'
gem 'rails', '5.2.4.4' #適したバージョンを指定

rails5系で現在最新のバージョンを指定。Ruby on Rails最新情報

docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: パスワード
      MYSQL_DATABASE: データベース名
    ports:
      - "3306:3306"

  web:
    build: .
    command: rails s -p 8080 -b '0.0.0.0'
    volumes:
      - .:/app_name
    ports:
      - "8080:8080"
    links:
      - db

command: rails s -p 8080 -b '0.0.0.0'

ports:
- "8080:8080"

cloud9ではport8080でサーバーに接続します。
その為、ホスト:コンテナ の両方を8080番で指定しています。

パスワードとデータベース名は任意の値を記載します。

これで設定を記載した3つのファイルと、空のGemfile.lockができました。

Railsプロジェクト作成

カレントディレクトリがプロジェクトディレクトリ(今回は/home/ec2-user/environment/myApp)であることを確認します。

そうしたらrails newを実行してアプリケーションの型を作ります。

$ docker-compose run web rails new . --force --database=mysql --skip-bundle

問題がなければプロジェクトディレクトリ配下にRailsのひな形が出来上がります。

$ ls
app  bin  config  config.ru  db  docker-compose.yml  Dockerfile  Gemfile  Gemfile.lock  lib  log  package.json  public  Rakefile  README.md  storage  test  tmp  vendor

データベース周りの設定のため/myApp/config/database.ymlを修正します。

database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: パスワード
  host: db

パスワードの箇所には先ほど"docker-compose.yml"で設定したのと同様の値を記載します。
ここで保存しようとすると、恐らくcloud9の上部に警告文が表示されます。

EACCES: permission denied

これはdatabase.ymlに対する書き込み権限が無いので保存できない旨の内容なので、権限を変更指定保存できるようにします。

/myApp/configに入り、ファイルの権限を確認します。

$ cd config
$ ls -l
-rw-r--r-- 1 root root 1657 Nov 29 19:06 database.yml

書き込み権限を変更し、反映されればdatabase.ymlを保存できるようになります。

$ sudo chmod 666 database.yml
$ ls -l
-rw-rw-rw- 1 root root 1657 Nov 29 19:06 database.yml

Dockerコンテナのビルド

プロジェクトディレクトリに戻り、コンテナを立ち上げます。

$ docker-compose build

PermissionError: [Errno 13] Permission denied: '/home/ec2-user/environment/myApp/config/master.key

このエラーが出るのは先ほど同様myApp/config/master.keyに対するパーミッションエラーなので、権限を変更します。

ディレクトリを移動して権限を確認。

$ cd config
$ls -l
-rw------- 1 root root   32 Nov 29 19:06 master.key

権限を変更して反映を確認します。

$ sudo chmod 666 master.key
$ ls -l
-rw-rw-rw- 1 root root   32 Nov 29 19:06 master.key

再トライ

$ docker-compose build
$ docker-compose up

これで私はうまくいきました。

データベースの作成

コンテナが起動したらショートカットキーAlt + Tでcloud9にターミナルをもう一つ立ち上げます。

開いたターミナルからプロジェクトファイルに移動して、データベースを立ち上げます。

$ cd myApp
$ docker-compose run web rails db:create

成功していれば Preview → Preview Running Application で「Yay! You’re on Rails!」が表示されます。
この時

**********.vfs.cloud9.ap-northeast-1.amazonaws.com で接続が拒否されました。

と表示されたら、右上の矢印ボタンを押して別タブで開けば表示されます。

サーバーを停止させる場合はCtrl + Cではなく、新しく開いた方のターミナルで下記を入力します。

$ docker-compose down

スクリーンショット 2020-11-26 184833.png

その他のエラー

Can't connect to local MySQL server through socket '/var/run/mysqld/mysqld.sock' (2)

database.ymlのhostの変更を忘れていると出ます。

Can't connect to MySQL server on 'db' (115)

コンテナが立ち上がり切っていない時に表示されます。
数秒待ってからリロードすれば表示内容が変わります。

Unknown database 'app_name_development'

データベースを作成していないとこの表示がされます。
新しいターミナルを開いてデータベースを作成すれば解消されます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cloud9 × Raisl × Docker × MySQL で環境構築

はじめに

cloud9を使ったDockerの環境構築の記事が少なく戸惑ったこと、自身のアウトプットのために執筆します。

別の記事でも記載していますが、現在転職活動中の身ですのでプロではありません。
間違いなどありましたらご指摘いただければ、自身でも調べ速やかに修正いたします。

参考記事:
丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)

ファイル内容は上記サイトから頂きましたが、port8080に対応するため一部編集を加えています。

目標

タイトルの構成で環境構築をし、「Yay! You’re on Rails!」を表示させる。

環境

Amazon linux
Ruby 2.6.3
Rails 5.2.4.4
MySQL 5.5.62
Docker 19.03.6
Docker-compose 1.27.4

やってきましょう

インスタンスのボリュームを増やす

cloud9を立ち上げてできるインスタンスはサイズが10GBとなっているので、容量が不足する可能性があります。
その都度不要ファイルを削除できればいいのですが、手っ取り早くコンソールから容量を増やしました。
(極少額の課金が発生するかもしれません、料金はご自身で調べてみてください)

まずはcloud9を立ち上げます。
その後EC2の画面に行くと、cloud9と同時に立ち上がったインスタンスがあります。

EC2のサイドメニューから下記の順番で選択します。
1.ボリューム
2.先ほど立ち上がったインスタンスのチェックボックス
3.アクション
4.ボリュームの変更

スクリーンショット 2020-11-29 190704.png

開いたウィンドウのサイズを16GBに変更
スクリーンショット 2020-11-29 190843.png

こうなればOK
スクリーンショット 2020-11-29 190917.png

インスタンスの状態がoptimizing(黄色のマーク)なので、10分程度放置し終わるのを待ちます。
スクリーンショット 2020-11-29 191119.png

状態が緑のマークになると、サイズも16GBとなり反映が確認できます。
スクリーンショット 2020-11-29 193501.png

cloud9のターミナルに移動します。
以下のコマンドで内部からもサイズ変更が反映されているかを確認します。

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
devtmpfs        483M   60K  483M   1% /dev
tmpfs           493M     0  493M   0% /dev/shm
/dev/xvda1       16G  8.6G  7.0G  55% /

もしリロードしても反映されていないようなら次のコマンドを試してみてください。

$ sudo growpart /dev/xvda 1
$ sudo resize2fs /dev/xvda1

Docker-Composeのインストール

インストールの前に このページ で最新のバージョンを確認しておいてください。

下の1.27.4の箇所を確認した最新のバージョンに書き換えてインストールします。
私は"/home/ec2-user/environment"ディレクトリで実行しています。

$ sudo curl -L https://github.com/docker/compose/releases/download/1.27.4/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose 

$ docker-compose コマンドを実行できるように権限を与えます。

$ sudo chmod +x /usr/local/bin/docker-compose

バージョンの確認をして、表示されればインストールは完了です。

$ docker-compose --version
docker-compose version 1.27.4, build 40524192

ファイルの準備

プロジェクトディレクトリを作成して、その中に移動します。

$ mkdir myApp
$ cd myApp

そうしたら必要なファイルをtouchコマンドで作成していきます。
ここでファイルは4つ用意します。

$ touch Dockerfile
$ touch Gemfile
$ touch Gemfile.lock
$ touch docker-compose.yml

通常はviコマンドなどでファイルを書いていきますが、cloud9はせっかくエディタが付いているのでそちらで作業しましょう。

なおGemfile.lockは記載せずに空のままで大丈夫です。

各ファイルの項目は Docker × Ruby on Rails × MySQLの環境構築 この記事を参考にしてください。

Dockerfile
FROM ruby:2.6.3
RUN apt-get update -qq && \
    apt-get install -y build-essential \ 
                       libpq-dev \        
                       nodejs           
RUN mkdir /app_name 
ENV APP_ROOT /app_name 
WORKDIR $APP_ROOT
ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock
RUN bundle install
ADD . $APP_ROOT

FROM ruby:2.6.3

cloud9にはあらかじめrubyがインストールされています。
以前同様の環境構築をした際、インストール済みのバージョンとここで指定したバージョンが異なりエラーになりました。(原因は他にあるのかもしれませんが、2つのバージョンが競合したのか?)

その為ここのrubyのバージョンは$ ruby -vで確認したインストール済みのバージョンと同様にしてあります。

Gemfile
source 'https://rubygems.org'
gem 'rails', '5.2.4.4'

rails5系で現在最新のバージョンを指定。Ruby on Rails最新情報

docker-compose.yml
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: パスワード
      MYSQL_DATABASE: データベース名
    ports:
      - "3306:3306"

  web:
    build: .
    command: rails s -p 8080 -b '0.0.0.0'
    volumes:
      - .:/app_name
    ports:
      - "8080:8080"
    links:
      - db

command: rails s -p 8080 -b '0.0.0.0'

ports:
- "8080:8080"

cloud9ではport8080でサーバーに接続します。
その為、ホスト:コンテナ の両方を8080番で指定しています。

パスワードとデータベース名は任意の値を記載します。

これで設定を記載した3つのファイルと、空のGemfile.lockができました。

Railsプロジェクト作成

カレントディレクトリがプロジェクトディレクトリ(今回は/home/ec2-user/environment/myApp)であることを確認します。

そうしたらrails newを実行してアプリケーションの型を作ります。

$ docker-compose run web rails new . --force --database=mysql --skip-bundle

問題がなければプロジェクトディレクトリ配下にRailsのひな形が出来上がります。

$ ls
app  bin  config  config.ru  db  docker-compose.yml  Dockerfile  Gemfile  Gemfile.lock  lib  log  package.json  public  Rakefile  README.md  storage  test  tmp  vendor

データベース周りの設定のため/myApp/config/database.ymlを修正します。

database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: パスワード
  host: db

パスワードの箇所には先ほど"docker-compose.yml"で設定したのと同様の値を記載します。
ここで保存しようとすると、恐らくcloud9の上部に警告文が表示されます。

EACCES: permission denied

これはdatabase.ymlに対する書き込み権限が無いので保存できない旨の内容なので、権限を変更指定保存できるようにします。

/myApp/configに入り、ファイルの権限を確認します。

$ cd config
$ ls -l
-rw-r--r-- 1 root root 1657 Nov 29 19:06 database.yml

書き込み権限を変更し、反映されればdatabase.ymlを保存できるようになります。

$ sudo chmod 666 database.yml
$ ls -l
-rw-rw-rw- 1 root root 1657 Nov 29 19:06 database.yml

Dockerコンテナのビルド

プロジェクトディレクトリに戻り、コンテナを立ち上げます。

$ docker-compose build

PermissionError: [Errno 13] Permission denied: '/home/ec2-user/environment/myApp/config/master.key

このエラーが出るのは先ほど同様myApp/config/master.keyに対するパーミッションエラーなので、権限を変更します。

ディレクトリを移動して権限を確認。

$ cd config
$ls -l
-rw------- 1 root root   32 Nov 29 19:06 master.key

権限を変更して反映を確認します。

$ sudo chmod 666 master.key
$ ls -l
-rw-rw-rw- 1 root root   32 Nov 29 19:06 master.key

再トライ

$ docker-compose build
$ docker-compose up

これで私はうまくいきました。

データベースの作成

コンテナが起動したらショートカットキーAlt + Tでcloud9にターミナルをもう一つ立ち上げます。

開いたターミナルからプロジェクトファイルに移動して、データベースを立ち上げます。

$ cd myApp
$ docker-compose run web rails db:create

成功していれば Preview → Preview Running Application で「Yay! You’re on Rails!」が表示されます。
この時

**********.vfs.cloud9.ap-northeast-1.amazonaws.com で接続が拒否されました。

と表示されたら、右上の矢印ボタンを押して別タブで開けば表示されます。

サーバーを停止させる場合はCtrl + Cではなく、新しく開いた方のターミナルで下記を入力します。

$ docker-compose down

スクリーンショット 2020-11-26 184833.png

その他のエラー

Can't connect to MySQL server on 'db' (115)

コンテナが立ち上がり切っていない時に表示されます。
数秒待ってからリロードすれば表示内容が変わります。

Unknown database 'app_name_development'

データベースを作成していないとこの表示がされます。
新しいターミナルを開いてデータベースを作成すれば解消されます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】Redmineの環境構築

AWSでRedmineの環境構築をしたときのメモ:writing_hand:

失敗

最初はAWS上にDBを作成し、新規DBとRedmineを接続しようとしていました。

試行1
③のブログの記載どおりにPowershellのコマンドから接続しようとしましたが、
"command not found"が表示されたので、いったん諦めました。
後で調べてみるとRubyの必要なファイルをインストールしていなかったことが原因だったようです。
Windows10にRubyGemsをインストールする方法

※AWSのコンソールに表示されているエンドポイントを正しく指定していても接続できない場合
→セキュリティグループでポート番号”3306”(DBで使用しているポート)を追加すると接続できるようになります。

▼その時参考にしたサイトたち
(MySQLのインストール方法)
Windows10にインストーラーでMySQLをインストールする方法
MySQLの開発環境を用意しよう(windows)
EC2-RDSを使ってRedmineをインストールする。ついでにサブディレクトリで。

(Redmineの設定方法)
Redmineのインストール

(MySQLの設定方法)
MySQL データベースを作成して接続する

成功

結局見つけた方法は、Redmine用のAMIから新規インスタンスを作成するというものでした。
こちらのブログの記載どおりにするとRedmineに接続できました…!

Todo

  • PowerShellのコマンドを勉強
  • MySQLのサーバー接続について調べる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Slackで投稿内容やファイルを閲覧するページを自作したお話 ①導入編

背景

Slackは非常に便利なコミュケーションツールなのですが、無料枠で使っていると閲覧上限1万件&ストレージ制限5GBが大きな制約となります。
自分の所属する団体では150人とかで使うので、1万件の制限があると3ヶ月分ぐらいしか閲覧できません。
(課金しろという話ですが、スタンダードプランにしても10万/月以上かかるので、、)

解決策??

ワークスペースを複数で運用したり、ログを取得するGASスプリクトをコピペして運用したりしていました。

参考) コピペのみ。Slackメッセージログを自動で保存する方法

問題点

ワークスペースを複数で運用すると、メッセージの共有ができないなど、コミュケーションツールとしての良さが失われてしまいます。また、GASスプリクトはログを残しておくという点では良いですが、後で閲覧するには不適です。

解決策: ログの取得や閲覧ページを自作する!

既存の方法で解決しないなら自分で作ってしまえ!という発想です。
とりあえず、

  • ログの取得・保存
  • 見やすい閲覧ページ

を実装しようと計画しました。

仕様

使用言語

  • React6.14.6
  • PHP7.4.4
  • MySQL5.7

フロントにはReact、バックエンドはPHPで実装しました。

Slackからデータを取得

SlackAPIでAppを作成してデータを取得します。APIを用いて以下のデータを取得しています。

  • ユーザー
  • チャンネル
  • チャット
  • ファイル

これらのデータはPHPで取得した後、MySQLでDBに格納します。

詳しくは別記事で説明します。

取得したデータを閲覧

フロントは勉強も兼ねてReactで実装しています。

また、DBに格納されたデータをフロントで取得するエンドポイントはPHPで作成しています。

詳しくは別記事で説明します。

まとめ

PHP+SlackAPIでデータ取得、MySQLでデータ格納して、PHPでエンドポイント作成、閲覧ページはReact+axiosという感じです。

編集後記

このシステムを作るのに、SlackAPIのドキュメントを何周もしました。
わからないところは、Google翻訳に助けられながらもなんとか理解できました。

続きの記事

時間あるときに書いていくので、気長にお待ち下さい、、

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む