- 投稿日:2020-06-03T23:31:47+09:00
MySQL エラーコードの意味を出力する
- 投稿日:2020-06-03T22:55:42+09:00
Flask の MySQL 接続と Life Cycle
Flask の MySQL 接続と Life Cycle
mysql has gone away が現れるようになり、前から気になってたflask appのlife cycleについて理解しようとしてみた。
結局原因が途中でわかったので尻すぼみだけど、記録として残しておく。わかってないこと
- appはいつ起動していつ終わるのか?
- urlに対する1 requestで app起動、コンテンツ返して終了、と思っていたが、gone awayってことはconnectionをkeepしている可能性がある
- MySQL接続はいつ切れるのか?
- MySQL(app) ではない独自の接続を多数持っている。これらは切断されないのか?
- 元のinstanceが廃棄されれば切断されると思うが、廃棄されているのか?
きれいなflaskは毎回切断している
以下のコードで検証
from flask import Flask from flask_mysqldb import MySQL app = Flask(__name__) app.config['MYSQL_USER'] = 'report_local' app.config['MYSQL_PASSWORD'] = 'report_local' app.config['MYSQL_HOST'] = 'sys-test-lowusage-db001-dbs4dev-jp2v-dev.lineinfra-dev.com' app.config['MYSQL_DB'] = 'mysql' app.config['MYSQL_PORT'] = 20306 app.config['MYSQL_CURSORCLASS'] = 'DictCursor' mysql = MySQL(app) @app.route('/') def users(): cur = mysql.connection.cursor() cur.execute('''SELECT CONNECTION_ID(); ''') # mysql connection idを取得 rv = cur.fetchall() return "app obj id = %s, %s" % (id(app), str(rv)) # <----- appのobject idも返す if __name__ == '__main__': app.run(debug=True)結果
- appは作り直されない
- MySQLは切断されていることがわかった
# first time app obj id = 4424594128, ({'CONNECTION_ID()': 7196},) # second time app obj id = 4424594128, ({'CONNECTION_ID()': 7197},) # and so on... app obj id = 4424594128, ({'CONNECTION_ID()': 7198},)appが作り直されないのは、app自体はあくまで初回の python run.py をした時点 = Web Serverを起動した時点で同時に生成されるから・・なんだろうか。
自分のいけてない MySQLdb で検証
show processlist; を見ていたら、やっぱり自分のflaskは切断してなかった。大量にコネクションが残ってた。
webサーバとアプリサーバが完全にわからない
appが作り直されない理由がよくわからない。
これは、根本的にwebサーバを理解していないのが問題なんだと思う。
webサーバは、起動するとソケット(ポート)をlistenにして接続を待ち受ける。
リクエストが来たら、定められた位置にあるファイルを取って渡すだけだ。
そこにあるのがアプリなら、アプリを起動する・・・・そう思っていけれど。web serverとpython applicationの間は、どうなっているんだろうか?
web server processを造ったとき、同時に受けてであるアプリも作られる。アプリ側も待機しないといけない。socketかportで、web-serverからのリクエストに答えるために
ずっと起動してlistenし続けている。これがアプリサーバか。
pythonのhttp serverだと、全部ひとつで完結していて、その結果アプリサーバが起動する・・・
当然、起動したときのものはずっと残る・・・
single threadの場合は、そのアプリが永遠に行き続ける・・のだろうか。
multi threadだったら? アプリサーバは、あくまでメインスレッドが起動するだけ?
リクエストに応じてappが作られる?むー・・それだとつじつまが合わない気がするsimple な web serverで検証
たぶんflaskのdev serverはこれを使ってるんじゃなかろうか。
import http.server import datetime class App(): def show(self): return "Hello %s" % datetime.datetime.now() app = App() class myHandler(http.server.BaseHTTPRequestHandler): def do_GET(self): self.send_response(200) self.send_header('Content-type','text/html') self.end_headers() self.wfile.write(str.encode("app id=%s, %s" % (str(id(app)), app.show()))) return server_address = ("", 8000) simple_server = http.server.HTTPServer(server_address, myHandler ) simple_server.serve_forever()参考 https://docs.python.org/3/library/http.server.html#http.server.HTTPServer
で、結局原因はclass変数だった
appが作り直されない理由がよくわからない。
これを書いて数週間経った今の理解で行くと、appインスタンスは作り直される。インスタンスは作り直されるが、app クラス は最初に作ったときのままだ。
pythonではクラス変数は最初の起動から永遠に再利用される。
今回の原因は、appから呼び出している自作クラスのmysql connection poolをいれる変数がclass変数だった。class MyClass: dbpool = {} # ←これが原因 def __init__(self): self.dbpool = {} # ←インスタンス変数にして解決したclass変数はアプリが最初に起動された状態を永遠に保持するので、コンテンツを返した後も残り続ける。
自分のclassは、self.dbpoolの中にconnectionオブジェクトが残っている限り再利用する構造にしていたので、
オブジェクトは残るが、オブジェクト自体は数時間で接続が切れている、という状態だった。
切れている接続を再利用すると mysql has gone away になる。いま考えれば con.close() する時に self.dbpool の中を空にしていなかったのかもしれない。
いずれにしても、インスタンス変数にした結果、アクセスのたびに self.dbpool が初期化されるようになったので
古いconnectionオブジェクトを再利用することはなくなった。そういえばself.dbpoolを空にする修正も一応した気がしてきた。個人的には ↓ に近いような問題だと思っていて、いや普通なのかもしれないけど、pythonなんだかなぁと感じたりしている。
Pythonのこれ、流石に言語としてどうなのという気持ちになるんだが… pic.twitter.com/degIU4tPeI
— 幸福のデータ科学㈱教祖テラモナギ (@teramonagi) May 21, 2020おしまい
- 投稿日:2020-06-03T20:26:43+09:00
MAMPのMySQLを利用するとことで面倒な環境構築せず、すぐにMySQL使えるように設定しよう!
まず、MAMPをダウンロードします。(無料)
https://www.mamp.info/en/downloads/
MAMP&MAMP PROをクリックしてダウンロード。ダウンロードしたら、インストールします。
インストール終了したら、アプリケーションに作成されます。
MAMPアイコンをMacのDockにドラッグ&ドロップしてショートカットの作成をオススメします。
アイコンをクリックすると起動します。
Start Serversをクリックすると緑色に点灯してMySQLが使えるようになります。
※Cloudは、赤く点灯しますが、無視してください。
MySQLが起動すると以下のphpMyAdminリンクにアクセスできます。
http://localhost:8888/phpMyAdmin/
phpMyAdminは、データベース、テーブル、カラムの作成、変更、削除が容易にできます。laravelの
.env
を設定しましょう。
デフォルトは、以下になっています。
DB_DATABASE=
データベース名を記述。
DB_PASSWORD=
Macの場合は、root
になります。
追加でDB_SOCKET=/Applications/MAMP/tmp/mysql/mysql.sock
を記述。phpMyAdminでデータベースを作成してみましょう。
新規作成をクリック。
データベース名を入力。
作成ボタンをクリックでデータベースが作成されました。
作成したデータベース名を
.env
のDB_DATABASE=
に記述。
ターミナルでmigrationします。
php artisan migrate設定したデータベースにmigrationされました。
phpMyAdminにアクセスして確認。
http://localhost:8888/phpMyAdmin/
作成したデータベース以下にmigrationされています。以上です。
参考になったら幸いです。
- 投稿日:2020-06-03T18:03:15+09:00
Rails gem deviseって?
Rails gem devise について
Ruby on Rails を学習中、deviseというユーザーも新規登録、ログイン機能などがgemをインストールするだけで簡単に作れちゃうというもの。非常に優れものです。
投稿主の備忘録もかねて手順を簡単に紹介していきます。
deviseをインストールする
Gemfilegem 'devise'ここで注意すべきなのは
device
ではなくdevise
私がプログラミング超初心者だった時このスペルミスでエラーが出てしまいました。
ここでgemをインストールするのでコマンドラインで
$ bundle install
しますまた、devise用のインストールコマンドがあるので実行します。
ターミナル$ rails g devise installここまででdeviseのインストールが完了します。
deviseのモデルを作成する
マイグレーションファイルに記載した情報を基にデータベースの型、制約を設定していきます。
ターミナル$ rails g devise user普段Railsアプリケーションでモデルを作成していく時には
$ rails g model user
というコマンドを打てばモデルの作成ができますがここではdeviseコマンドでモデルを作成します。このコマンドを実行後
model/user.rb
とuser用のマイグレーションファイルが作成されていれば成功です。ユーザーのマイグレーションファイルには
20200603_devise_create_users.rbclass DeviseCreateUsers < ActiveRecord::Migration[5.0] def change create_table :users do |t| ## Database authenticatable t.string :name, null: false t.string :email, null: false, default: "" t.string :encrypted_password, null: false, default: "" # 〜省略〜 end add_index :users, :name, unique: true # usersの情報全てに検索がかけれるようにadd_indexを貼るnameカラム、eーmailカラムなどが設定され、それぞれnull: falseで制約がかけられておりnullであればユーザーの新規登録でエラー表示されるようになります。
user.rbにはバリデーションもかけておきましょう。
user.rbclass User < ApplicationRecord # Include default devise modules. Others available are: # :confirmable, :lockable, :timeoutable and :omniauthable devise :database_authenticatable, :registerable, :recoverable, :rememberable, :validatable validates :name, presence: true, uniqueness: true # nameカラムが空でないこと、重複したnameは登録できないようにバリデーションをかけている endバリデーションには様々な制約を設けることができ、もっと知りたいという方はこちらから!
https://qiita.com/h1kita/items/772b81a1cc066e67930eこれでデータベースと紐づくマイグレーションファイルとモデルの設定が完了しました。
ターミナル$ rails db:migrateマイグレーションファイルをマイグレートしておきましょう。
ビューファイルの準備
ビューファイルもdeviseコマンド一発で作成できます。
ターミナル$ rails g devise:views usersこれで簡単ではありますがユーザーの新規登録とログイン機能の実装ができました。
- 投稿日:2020-06-03T16:20:43+09:00
パンくず機能
自分の環境
Ruby 2.5.1p57
Rails 5.2.4.2
mysql2 (0.5.3)なぜパンくずを実装しようと思ったか
プログラミングスクールでフリーマーケットアプリケーションを作った際に
どこのページにいるかを目視できるようにする為です。パンくずという言葉の由来
パンくずリストという語源は、童話「ヘンゼルとグレーテル」の話の中に主人公の兄弟が森に入るときに、迷子にならないように自分たちが通ってきた道にパンくずを置いていったエピソードが由来となっています。
パンくずのメリット
ユーザビリティが高くなる
パンくずリストを設置することで「自分が今サイト内のどこにいるのか」や「サイトの構造」を認識しやすくなり、結果として使いやすさを高めることができます。
パンくずリストとは
① パンくず機能のgem導入
Gemfileの一番下にgem gretelを記述
Gemfile.gem gretelターミナルでbundle installをする
$bundle install② ファイルの設定
ターミナルで実行する
$ rails generate gretel:install実行すると下記のファイルが生成されます
config.breadcrumbs.rbcrumb :root do link "トップページ", root_path end crumb :loginer do link "ログインページ", new_user_session_path end・ :loginer ← 設定ファイルを呼び出します。
・ "ログインページ" ← パンくずリストに表示される名称です。
・ new_user_session_path ← 呼び出し元のパスを指定します。③ ビューファイルの設定
new.html.haml-# config/breadcrumbs.rbに定義したmypageを呼び出し - breadcrumb :loginer -# 下記を記述した箇所にパンくずリストが表示される。 = breadcrumbs separator: " › "・ separator ← パンくずの区切り文字を指定。「&rsaquo」は出力されると「›」になります。
パンくずリストの表示は、一般的に全ページに表示する事ができるようlayout/breadcrumbs.html.hamlに記載する事が多いと思いますが、今回は全部のページではなく一部のページに投稿させたいので、部分テンプレート化して呼び出しています。
layout/_breadcrumbs.html.haml.breadcrumbs = breadcrumbs pretext: "",separator: " › ", class: "breadcrumbs-list"
実際に記入するとこのように表示されますnew.html.haml- breadcrumb :loginer = breadcrumbs separator: " › "④ 親の設定
config.breadcrumbs.rbのcrumb、endの間にparentを設定する事で親を設定する事ができます。商品詳細ページを親に設定して商品編集ページをリストに表示させましょう。
config.breadcrumbs.rbcrumb :root do link "トップページ", root_path end crumb :shower do link "商品詳細ページ", item_path end crumb :editer do link "商品編集ページ", edit_item_path parent :shower end
以上がパンくず機能の実装のやり方です。
今回参考にさせていただいた記事
https://qiita.com/Azure0701/items/16de34a0010eb7f05d89
https://www.asobou.co.jp/blog/web/breadcrumb-list
https://qiita.com/you8/items/d2d37a745060b79c112f
- 投稿日:2020-06-03T15:24:02+09:00
Rails Herokuデプロイ手順
プログラミング初学者のため訂正がありましたらご指摘ください。
gitをインストールしている前提です。自身の環境
- Ruby 2.5.1
- Ruby on Rails 5.2.4.1
- MySQL (gem 'mysql2', '>= 0.4.4', '< 0.6.0')
デプロイの流れ
- The Heroku CLIの設定
- Herokuにログイン
- Herokuにデプロイ
Herokuとは
HerokuとはWebアプリケーションを簡単に全世界に公開できるクラウドプラットフォームです。
参考)HEROKU とは
以下のURLからHerokuのユーザー登録を行います。ユーザー登録は無料です。
1. The Heroku CLIの設定
The Heroku CLIをインストールすることで、Herokuのコマンドが使えるようになります。
下記のリンクからOSを指定してダウンロードしてインストールを完了させてください。
https://devcenter.heroku.com/articles/heroku-cli
2. Herokuにログイン
The Heroku CLIをインストールしたので、ターミナル上でHerokuのコマンドが使えるようになりました。
早速ターミナルからHerokuにログインしましょう。
Herokuへアップロードしたいアプリのディレクトリへ移動し、「heroku loginコマンド」を実行してください。
loginコマンド実行後、herokuに登録したメールアドレスとパスワードの入力が必要です。
$ cd app # appの部分を自分の作ったアプリ名にします $ heroku login # herokuにログインする Enter your Heroku credentials: Email:メールアドレスとパスワードの入力が完了すると以下のように表示されます。
Logged in as 入力したメールアドレス3. Herokuにデプロイ
HerokuではPostgreSQLデータベースを使います。
なので、PostgreSQLをインストールしていきます。
以下のコマンドをターミナルで実行します。(既にインストールされている方はインストールしなくて大丈夫です。)
$ brew install postgresqlインストールが完了したら、本番 (production) 環境にpg gemをインストールしてRailsがPostgreSQLと通信できるようにします。
以下のコードをGemfileの最下部に追加してください。
Gemfile.group :production do gem 'pg' endpg gemは本番用のgemでローカル環境にはインストールしないようにします。その場合、bundle installに--without productionを追加します。このフラグを追加することで、pg gemはローカル環境には反映されないようになります。それでは以下のコマンドを実行します。
$ bundle install --without productionbundle installの本番環境用
次に「heroku create アプリ名」コマンドでheroku上にアプリケーションを作成します。 以下のコマンドを実行します。
$ heroku create上記のようにアプリ名を入力しないと自動で名前をつけてくれます。
一度登録した名前は使えないので注意してください上記のコマンドを実行すると、以下のような結果が表示されます。
Creating app... done, ⬢ app(アプリ名) https://app(アプリ名).herokuapp.com/ | https://git.heroku.com/app(アプリ名).githttps://~~.herokuapp.com/が上記のコマンドで作成されたサブドメインです。 この時点でブラウザに表示可能ですが、今はまだ何もありません。デプロイしてWebページを表示させましょう。
RailsアプリケーションをHerokuにデプロイするには、まずGitを使ってHerokuにリポジトリをプッシュします。
$ git add . $ git commit -m "initial commit" $ git push heroku master上手く行くと、下記のようにremote: Verifying deploy... done.と表示されます。
. . . remote: Verifying deploy... done. To https://git.heroku.com/app(アプリ名).git * [new branch] master -> master次に以下コマンドでmigrationを実行します。ローカル環境で行なっていたrails db:migrateのコマンドを本番環境でも行うというイメージです。
$ heroku run rails db:migrate上記のコマンドを実行したら、以下のコマンドを実行してWebページを表示させましょう。
$ heroku open以上です。
参考記事
https://qiita.com/kazukimatsumoto/items/a0daa7281a3948701c39
https://qiita.com/NaokiIshimura/items/eee473675d624a17310f
- 投稿日:2020-06-03T13:17:58+09:00
MySQL5.7でアプリが動かなくなる原因の1つ
MySQLのバージョンを5.7にあげたら動かなくなった
MySQL8がリリースされているものの、まだまだMySQL5系をご利用されている場合は多いかと思います。今回はphpのアプリで利用しているMySQLのバージョンを5.6→5.7に入れ替えたときに急にアプリが動かなくなりました。
原因:sql_modeの設定
原因はMySQLの設定項目であるsql_modeでした。旧環境(5.6)と新環境(5.7)で
この設定が異なっていたため、SQLエラーが頻発していました。
(sql_modeの解説は他に詳しく書いている方が多くいると思うので省略します。)コマンド実行で変更可能ですが運用的には
/etc/my.cnf
に[mysqld] ... sql_mode=NO_AUTO_VALUE_ON_ZERO,STRICT_ALL_TABLESみたいに明示的に指定して固定しておいたほうが間違いないかと。
sql_modeのデフォルト設定は5.7以降よく変わっているようです。(参考)
- 投稿日:2020-06-03T00:56:22+09:00
MySQL 5.7で文字列末尾から特定の文字列が出現するまでの文字列を抽出する
TL;DR
とある案件で、
URLを含む本文
から本文末尾に出現するURL
のみ抽出して本文とURLを分割してほしいと依頼が来たので、文字列末尾から特定の文字列が出現するまでの文字列を抽出するクエリ
を作成しました。SELECT RIGHT(body, INSTR(REVERSE(body), REVERSE('http://')) + CHAR_LENGTH('http://') - 1) AS url FROM articles;環境
- MySQL 5.7
使用する関数
名前 説明 INSTR 部分文字列が最初に出現する位置のインデックスを返します REVERSE 文字列内の文字を逆順に並べ替えます RIGHT 右端から指定された数の文字を返します 引用元: MySQL 5.6 リファレンスマニュアル / 関数と演算子 / 文字列関数 1
クエリ解説
サンプルのテーブル
mysql> SELECT * FROM articles; +----+---------------------------------------------------+ | id | body | +----+---------------------------------------------------+ | 1 | 本文http://example.com 本文http://example.com | +----+---------------------------------------------------+ 1 row in set (0.00 sec)検索したい文字列が1文字の場合
- REVERSE関数で
本文の文字列
を逆順に並べ替え、INSTR関数で検索文字列
が最初に出現する位置のインデックスを取得1.で取得したインデックス
=本文の文字列末尾から検索文字列が出現するまでの文字列の長さ
になるので、そのままRIGHT関数に渡すmysql> SELECT INSTR(REVERSE(body), 'h') AS `index` FROM articles; +-------+ | index | +-------+ | 18 | +-------+ 1 row in set (0.00 sec) mysql> SELECT RIGHT(body, INSTR(REVERSE(body), 'h')) AS url FROM articles; +--------------------+ | url | +--------------------+ | http://example.com | +--------------------+ 1 row in set (0.01 sec)検索したい文字列が2文字以上の場合
- REVERSE関数で
本文の文字列
と検索したい文字列
を逆順に並べ替え、INSTR関数で検索文字列
が最初に出現する位置のインデックスを取得1.で取得したインデックス
=本文の文字列末尾から検索文字列の末尾が出現するまでの文字列の長さ
になるので、検索文字列の長さ
を足して1引いてからRIGHT関数に渡すmysql> SELECT INSTR(REVERSE(body), REVERSE('http://')) AS `index` FROM articles; +-------+ | index | +-------+ | 12 | +-------+ 1 row in set (0.00 sec) mysql> SELECT RIGHT(body, INSTR(REVERSE(body), REVERSE('http://')) + CHAR_LENGTH('http://') - 1) AS url FROM articles; +--------------------+ | url | +--------------------+ | http://example.com | +--------------------+ 1 row in set (0.01 sec)まとめ
実際に使ったときはCASE式とサブクエリを使って、本文とURLを分割して別々のカラムに保存しました。
開発や運用の都合で、ときどきこの手の泥臭いクエリが必要になることがあるので、文字列操作の関数は一通り目を通しておこうと思いました。追記(2020/06/03)
とある先輩エンジニアに今回のケースならSUBSTRING_INDEX関数で取得できるとアドバイスを貰ったので追記します。
SELECT CONCAT('http://', SUBSTRING_INDEX(body, 'http://', -1)) AS url FROM articles;SUBSTRING_INDEX関数は区切り文字が指定された回数出現する前の部分文字列を返しますが、第3引数に負の数を指定すると文字列の末尾から検索します。
mysql> SELECT SUBSTRING_INDEX(body, 'http://', -1) FROM articles; +--------------------------------------+ | SUBSTRING_INDEX(body, 'http://', -1) | +--------------------------------------+ | example.com | +--------------------------------------+ 1 row in set (0.00 sec)あとはCONCAT関数で区切り文字列と結合するだけですね。
mysql> SELECT CONCAT('http://', SUBSTRING_INDEX(body, 'http://', -1)) AS url FROM articles; +--------------------+ | url | +--------------------+ | http://example.com | +--------------------+ 1 row in set (0.01 sec)
MySQL 5.7のリファレンスは日本語化されていなかったので5.6にしておきました。 ↩