かみぽわーる kamipo's blog 2022-08-01T10:50:58+09:00 kamipo Hatena::Blog hatenablog://blog/13208692334729888811 HPVワクチンを接種してきた hatenablog://entry/4207112889904775228 2022-08-01T10:50:58+09:00 2022-08-01T10:50:58+09:00 先日、HPVワクチン(ガーダシル9価)を接種してきた。 HPVワクチン(ガーダシル9価)接種してきたᴖᴗᴖ pic.twitter.com/ADE8qNKfSd— Ryuta Kamizono (@kamipo) July 21, 2022 HPV(ヒトパピローマウイルス)は、性的接触のあるひとはだいたいが生涯で一度は感染するとされている一般的なウイルスで、子宮頸がんを始め、肛門がん、膣がんなどのがんや尖圭コンジローマ等多くの病気の発生に関わっています。特に、近年若い女性の子宮頸がん罹患が増えていると言われていて、年間約1万人が疾患して、約2900人がこの病気で命を落としているそうです。 HP… <p>先日、HPVワクチン(ガーダシル9価)を接種してきた。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">HPVワクチン(ガーダシル9価)接種してきたᴖᴗᴖ <a href="https://t.co/ADE8qNKfSd">pic.twitter.com/ADE8qNKfSd</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1550037922325606400?ref_src=twsrc%5Etfw">July 21, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>HPV(<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D2%A5%C8%A5%D1%A5%D4%A5%ED%A1%BC%A5%DE%A5%A6%A5%A4%A5%EB%A5%B9">ヒトパピローマウイルス</a>)は、性的<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%DC%BF%A8">接触</a>のあるひとはだいたいが生涯で一度は感染するとされている一般的なウイルスで、子宮頸がんを始め、肛門がん、膣がんなどのがんや尖圭コンジローマ等多くの病気の発生に関わっています。特に、近年<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BC%E3%A4%A4%BD%F7">若い女</a>性の子宮頸がん罹患が増えていると言われていて、年間約1万人が疾患して、約2900人がこの病気で命を落としているそうです。</p> <p>HPVワクチンは小学校6年~高校1年相当の女の子は定期接種といって無銭(公費)でワクチンを接種できます。が、2013年にいろいろあって定期接種の積極的勧奨を差し控えるという事態になってしまい、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%E1%A5%EA">アメリ</a>カやオーストラリアでは接種率が約8割に達する中、日本での接種率は1%以下となっていたそうです。が、約9年のときを経てついに2022年4月に積極的勧奨が再開されました!</p> <p>積極的勧奨が差し控えられた当時、ワクチンの副反応がやべえみたいな感じでメディアで煽られたために、親御さんがそんなやべえもんうちの子に打たせられるか!みたいな感じで定期接種を受けられずに大人になってしまったひともいるかと思うんですけど、ただいまキャッチアップ接種キャンペーン期間中(2022年4月〜2025年3月)につき、誕生日が1997年4月2日~2006年4月1日の16歳〜25歳の女性も無銭(公費)でワクチンを接種することができます!</p> <p>定期接種を受けられなかったキャッチアップ接種対象のひとには予診票が届くことになってるんですが、このキャンペーンけっこう急に決まって対応に手が回ってない<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BC%AB%BC%A3">自治</a>体もあるらしいんで、キャッチアップ接種受けたいけどまだ予診票届いてないってひとは自分がお住まいの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BC%AB%BC%A3">自治</a>体にキャッチアップ接種受けたいんで予診票送ってもらっていいですか?って問い合わせしてみるといいかもしれないです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.mhlw.go.jp%2Fstf%2Fseisakunitsuite%2Fbunya%2Fkenkou%2Fhpv_catch-up-vaccination.html" title="ヒトパピローマウイルス(HPV)ワクチンの接種を逃した方へ~キャッチアップ接種のご案内~" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.mhlw.go.jp/stf/seisakunitsuite/bunya/kenkou/hpv_catch-up-vaccination.html">www.mhlw.go.jp</a></cite></p> <p>あとキャッチアップ接種のこと知らなくて自腹でHPVワクチン接種してもうたわってひとも、任意接種の費用を助成してくれる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BC%AB%BC%A3">自治</a>体もあるので自分がお住まいの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BC%AB%BC%A3">自治</a>体に確認してみるといいかもです。ちなみに僕が住む新宿区は任意接種の費用を助成してくれるみたいです。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fwww.city.shinjuku.lg.jp%2Ffukushi%2Fyobo01_001033.html" title="ヒトパピローマウイルス(HPV)ワクチンの接種について:新宿区" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://www.city.shinjuku.lg.jp/fukushi/yobo01_001033.html">www.city.shinjuku.lg.jp</a></cite></p> <p>あと日本ではまだ定期接種は女性だけが対象なのだけど、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%E1%A5%EA">アメリ</a>カやイギリスとかでは男子も定期接種の対象で、オーストラリアでは15歳の男女の接種率は80%超えてて子宮頸がんの原因になるハイリスク群の型のHPV感染がめちゃくちゃ減少しているそうです。</p> <p>日本でも、男性のHPVワクチン接種を助成する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BC%AB%BC%A3">自治</a>体も出てきてて、この流れがいろんな<a class="keyword" href="http://d.hatena.ne.jp/keyword/%BC%AB%BC%A3">自治</a>体に広まっていって、いずれは性別に関わらず定期接種できるようになったらめっちゃいいと思う!</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fnlab.itmedia.co.jp%2Fnl%2Farticles%2F2206%2F09%2Fnews201.html" title="「全国初」“男性のHPVワクチン接種”助成制度が始まる 「全国に広まってほしい!」青森県平川市に称賛の声【追記あり】" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;" loading="lazy"></iframe><cite class="hatena-citation"><a href="https://nlab.itmedia.co.jp/nl/articles/2206/09/news201.html">nlab.itmedia.co.jp</a></cite></p> <p>僕はもういい年したおっさんなので、いまさら僕がHPVワクチン接種したところで、僕自身がHPV感染を予防するという意味での効果は正直あんまりないと思うんだけど、ひとに勧めるワクチンを自分は接種してないというのもどうかと思うし、こういうのは気持ちの問題なんでね、おっさんにはあんまり効果ないと思うけど若ければ若いうちに接種したほうが予防効果は期待できるんで、キャッチアップ接種とかあってお得な今がHPVワクチン接種を検討する<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D3%A5%C3%A5%B0%A5%A6%A5%A7%A1%BC%A5%D6">ビッグウェーブ</a>に乗る絶好のチャンスなんじゃないかなと思います僕は。</p> <ul> <li><p><a href="https://www.mhlw.go.jp/bunya/kenkou/kekkaku-kansenshou28/index.html">&#x30D2;&#x30C8;&#x30D1;&#x30D4;&#x30ED;&#x30FC;&#x30DE;&#x30A6;&#x30A4;&#x30EB;&#x30B9;&#x611F;&#x67D3;&#x75C7;&#xFF5E;&#x5B50;&#x5BAE;&#x9838;&#x304C;&#x3093;&#xFF08;&#x5B50;&#x5BAE;&#x3051;&#x3044;&#x304C;&#x3093;&#xFF09;&#x3068;HPV&#x30EF;&#x30AF;&#x30C1;&#x30F3;&#xFF5E;&#xFF5C;&#x539A;&#x751F;&#x52B4;&#x50CD;&#x7701;</a></p></li> <li><p><a href="https://www.mhlw.go.jp/stf/seisakunitsuite/bunya/kenkou/hpv_catch-up-vaccination.html">&#xFF28;&#xFF30;&#xFF36;&#x30EF;&#x30AF;&#x30C1;&#x30F3;&#x306E;&#x63A5;&#x7A2E;&#x3092;&#x9003;&#x3057;&#x305F;&#x65B9;&#x3078;&#xFF5E;&#x30AD;&#x30E3;&#x30C3;&#x30C1;&#x30A2;&#x30C3;&#x30D7;&#x63A5;&#x7A2E;&#x306E;&#x3054;&#x6848;&#x5185;&#xFF5E;&#xFF5C;&#x539A;&#x751F;&#x52B4;&#x50CD;&#x7701;</a></p></li> <li><p><a href="https://www.mhlw.go.jp/stf/seisakunitsuite/bunya/kenkou/hpv_qa.html">HPV&#x30EF;&#x30AF;&#x30C1;&#x30F3;&#x306B;&#x95A2;&#x3059;&#x308B;Q&amp;A&#xFF5C;&#x539A;&#x751F;&#x52B4;&#x50CD;&#x7701;</a></p></li> </ul> kamipo MySQL 8.0のクライアントでMySQL 5.7のサーバーに接続するとcharsetが設定されないかもしれない hatenablog://entry/26006613694590813 2021-02-21T19:31:28+09:00 2021-02-21T19:44:38+09:00 mysql_options(mysql, MYSQL_SET_CHARSET_NAME, cs_name) だけして mysql_real_connect(mysql, ...) した後SHOW VARIABLESしてみたら接続のcharsetが設定済みの挙動をするんやけどmysql_real_connectからの一連のコード読んでもどこでそれが起きるのかわからん誰かたすけて🥲https://t.co/ZScoD3tIQ8— Ryuta Kamizono (@kamipo) February 20, 2021 MySQLのハンドシェイクパケットにcollation_idを1バイトだけ入れられる… <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr"><a class="keyword" href="http://d.hatena.ne.jp/keyword/mysql">mysql</a>_options(<a class="keyword" href="http://d.hatena.ne.jp/keyword/mysql">mysql</a>, <a class="keyword" href="http://d.hatena.ne.jp/keyword/MYSQL">MYSQL</a>_SET_CHARSET_NAME, cs_name) だけして <a class="keyword" href="http://d.hatena.ne.jp/keyword/mysql">mysql</a>_real_connect(<a class="keyword" href="http://d.hatena.ne.jp/keyword/mysql">mysql</a>, ...) した後SHOW VARIABLESしてみたら接続のcharsetが設定済みの挙動をするんやけど<a class="keyword" href="http://d.hatena.ne.jp/keyword/mysql">mysql</a>_real_connectからの一連のコード読んでもどこでそれが起きるのかわからん誰かたすけて🥲<a href="https://t.co/ZScoD3tIQ8">https://t.co/ZScoD3tIQ8</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1363013345754771459?ref_src=twsrc%5Etfw">February 20, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr"><a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>のハンドシェイクパケットにcollation_idを1バイトだけ入れられるところがあって、charset name のデフォルトの collation_id を送っています。<br><br>クライアントとサーバーのバージョンが違うとデフォルトのcollation_idが違うことがあって罠になります。</p>&mdash; Inada <a class="keyword" href="http://d.hatena.ne.jp/keyword/Naoki">Naoki</a> (@methane) <a href="https://twitter.com/methane/status/1363016730751066116?ref_src=twsrc%5Etfw">February 20, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">この場合デフォルトのcollationはクライアントライブラリ (libmysqlclient) に定義されていて、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0.どこか で utf8mb4 のデフォルトのcollationが変わったので、バージョンアップしたら動作が変わった!ってなり得ます。</p>&mdash; Inada <a class="keyword" href="http://d.hatena.ne.jp/keyword/Naoki">Naoki</a> (@methane) <a href="https://twitter.com/methane/status/1363017390385029127?ref_src=twsrc%5Etfw">February 20, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>methaneさんに<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>のハンドシェイクパケットにはcollation_idが入ってることを教えてもらったので、本当にHandshake Response Packetからcharsetを設定しているのか調べてみた。</p> <p><a href="https://dev.mysql.com/doc/internals/en/connection-phase.html">MySQL :: MySQL Internals Manual :: 14.2 Connection Phase</a></p> <p>Handshake Response Packetの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DA%A5%A4%A5%ED%A1%BC%A5%C9">ペイロード</a>の構造を見ると先頭から8バイト目にたしかにcharacter_setのidを1バイト入れられるっぽい。</p> <p><a href="https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse">MySQL :: MySQL Internals Manual :: 14.2.5 Connection Phase Packets</a></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0のdefault collationのid一覧はこれ。</p> <p><a href="https://dev.mysql.com/doc/internals/en/character-set.html">MySQL :: MySQL Internals Manual :: 14.1.4 Character Set</a></p> <p>このパケットは、サーバーからのInitial Handshake Packetをパースしたあと、最初にレスポンスするときに<code>mysql_fill_packet_header</code>で作られてサーバーに送られる。</p> <p><a href="https://github.com/mysql/mysql-server/blob/mysql-8.0.23/sql-common/client.cc#L4060">https://github.com/mysql/mysql-server/blob/mysql-8.0.23/sql-common/client.cc#L4060</a></p> <p>サーバーは<code>parse_client_handshake_packet</code>でクライアントからのレスポンスの先頭から8バイト目を<code>charset_code</code>として取り出している。</p> <p><a href="https://github.com/mysql/mysql-server/blob/mysql-8.0.23/sql/auth/sql_authentication.cc#L2476">https://github.com/mysql/mysql-server/blob/mysql-8.0.23/sql/auth/sql_authentication.cc#L2476</a></p> <p><a href="https://github.com/mysql/mysql-server/blob/mysql-8.0.23/sql/auth/sql_authentication.cc#L2581">https://github.com/mysql/mysql-server/blob/mysql-8.0.23/sql/auth/sql_authentication.cc#L2581</a></p> <p>最終的に<code>thd_init_client_charset</code>で取り出した<code>cs_number</code>から現在のスレッドハンドルのcharsetを設定している。</p> <p><a href="https://github.com/mysql/mysql-server/blob/mysql-8.0.23/sql/sql_connect.cc#L422-L423">https://github.com/mysql/mysql-server/blob/mysql-8.0.23/sql/sql_connect.cc#L422-L423</a></p> <p>これにより、<code>mysql_options(mysql, MYSQL_SET_CHARSET_NAME, cs_name)</code>して<code>mysql_real_connect(mysql, ...)</code>すると<code>cs_name</code>のdefault collationがコネクションのcharsetとして設定されるわけですね。</p> <p>ここで表題の "<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0のクライアントで<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7のサーバーに接続するとcharsetが設定されないかもしれない" についてなんですが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0.1からutf8mb4のdefault collationがutf8mb4_general_ci (id: 45)からutf8mb4_0900_ai_ci (id: 255)に変更されたため、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0のクライアントがuff8mb4でサーバーに接続するとid: 255のcs_numberを送るけど<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7はid: 255のcs_numberを知らないのでサーバー側のデフォルトの設定が採用されるという仕組み。</p> <p>理想的なケースでは、サーバーに接続したらcharsetは適切に設定されるけど、最悪のケース、サーバーは<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7でサーバー側のcharsetはutf8mb4に設定されておらず<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0のクライアントからutf8mb4で接続するケースではコネクションのcharsetはutf8mb4に設定されない。</p> <p>一応、接続後に<code>SET NAMES utf8mb4</code>すればサーバー側のutf8mb4のdefault collationが設定されるが、最悪のケースをカバーするために適切に設定してるひとには必要ない処理が増えて損をすることになるのでなんとか回避したい気持ちがあるけど、現状はそういう感じ。</p> kamipo 新宿うまいカレー屋多すぎん? hatenablog://entry/26006613688141177 2021-02-06T21:15:59+09:00 2021-02-07T01:45:43+09:00 いろいろあって自由な時間を活用してなんか人生が充実するようなことしたいなということで、ランチのおいしいお店を開拓しようというのをやっていた。その中でも新宿うまいカレー屋多すぎん?と思ったので行ったことのある新宿のカレー屋さんを紹介します。 草枕 三丁目と御苑前のあいだぐらいでちょっと遠いんだけど新宿でいちばん好きなカレー。🍆🍅🐔がうますぎるので🍆🍅🐔ばっかり食ってる。 🍆🍅🐔🍛🍺 pic.twitter.com/8li8u6AgGL— Ryuta Kamizono (@kamipo) October 30, 2020 東京ドミニカ 草枕うますぎるけど遠いので、近場でうまいスープカレー食いたいと… <p>いろいろあって自由な時間を活用してなんか人生が充実するようなことしたいなということで、ランチのおいしいお店を開拓しようというのをやっていた。その中でも新宿うまいカレー屋多すぎん?と思ったので行ったことのある新宿のカレー屋さんを紹介します。</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%F0%CB%ED">草枕</a></li> </ul> <p>三丁目と御苑前のあいだぐらいでちょっと遠いんだけど新宿でいちばん好きなカレー。🍆🍅🐔がうますぎるので🍆🍅🐔ばっかり食ってる。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="und" dir="ltr">🍆🍅🐔🍛🍺 <a href="https://t.co/8li8u6AgGL">pic.twitter.com/8li8u6AgGL</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1322014829289197569?ref_src=twsrc%5Etfw">October 30, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <ul> <li>東京ドミニカ</li> </ul> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%C1%F0%CB%ED">草枕</a>うますぎるけど遠いので、近場でうまい<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A1%BC%A5%D7%A5%AB%A5%EC%A1%BC">スープカレー</a>食いたいときによく行く。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">京鴨の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A1%BC%A5%D7%A5%AB%A5%EC%A1%BC">スープカレー</a>🦆🍛 <a href="https://t.co/3fvtqKhyGE">pic.twitter.com/3fvtqKhyGE</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1306830498686095362?ref_src=twsrc%5Etfw">September 18, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <ul> <li>魯珈</li> </ul> <p>ランチタイムに限定35食ぐらいしか提供してなくて朝ノートに記帳して整番ゲットしないと食べられない貴重なお店。つぎいつ来れるかわからないのでこの日はルーとライスおかわりしました🍛</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">ねんがんのろかプレートをてにいれたぞ!🍛 <a href="https://t.co/kb7hhjqGVv">pic.twitter.com/kb7hhjqGVv</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1303917746866892801?ref_src=twsrc%5Etfw">September 10, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <ul> <li>半月</li> </ul> <p>魯珈の近くにあるお店。整番ゲットしなくても入れてうまい。お店の名前の半月はお皿に盛ったルーが半月の形だかららしいです🌛</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">2種盛り🌜🍛🌛 <a href="https://t.co/BER8SnIALF">pic.twitter.com/BER8SnIALF</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1319107387282747392?ref_src=twsrc%5Etfw">October 22, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <ul> <li>FISH</li> </ul> <p>なんかどこからともなくすごいいい匂いしてくるから気になって調べたら人気店だった。激辛チキンがマジで激辛すぎて震えた:;(∩´﹏`∩);:</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">激辛チキン&amp;<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AD%A1%BC%A5%DE%A5%AB%A5%EC%A1%BC">キーマカレー</a>🍛🐔🔥 <a href="https://t.co/cNfNxU1dhe">pic.twitter.com/cNfNxU1dhe</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1318028028169965570?ref_src=twsrc%5Etfw">October 19, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <ul> <li>極哩</li> </ul> <p>野菜の彩りがオシャレで映え重視で来た。かわいい店員さんにおすすめされて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B0%A5%A2%A5%D0">グアバ</a>ジュースも頼みました(ちょろい)</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">極哩2種盛り🍛 <a href="https://t.co/TsGSjqoIRA">pic.twitter.com/TsGSjqoIRA</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1353542483154624512?ref_src=twsrc%5Etfw">January 25, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <ul> <li>アチャカナ</li> </ul> <p>ここで紹介した中だと唯一ライスかナンか選べるお店だったのでナンにしました🇮🇳</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">アチャカナにきた🍛 <a href="https://t.co/gamBZhaWqg">pic.twitter.com/gamBZhaWqg</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1324577031825186817?ref_src=twsrc%5Etfw">November 6, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <ul> <li>イエローカンパニー</li> </ul> <p>めっちゃオーソドックスな<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A1%BC%A5%D7%A5%AB%A5%EC%A1%BC">スープカレー</a>という感じ。カレーに合いそうなビールがけっこうある🍺</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">チキン&amp;ベジタブル🐔🐣🥕🎃🥬🍄🥔 <a href="https://t.co/ExBiUFB1Z1">pic.twitter.com/ExBiUFB1Z1</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1329306105411825664?ref_src=twsrc%5Etfw">November 19, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <ul> <li>酔っこら処</li> </ul> <p>ほりさんのカレーおいしいです(^q^)</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">ほりさんのカレーだよみーみちゃん🍛 <a href="https://t.co/uul1f9VNXF">pic.twitter.com/uul1f9VNXF</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1339261256998350848?ref_src=twsrc%5Etfw">December 16, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>他にもおいしいお店あったら教えてください行ってみます🍛</p> kamipo 2021年はブログを書くのをがんばろうという話 hatenablog://entry/26006613685659222 2021-02-01T00:48:02+09:00 2021-02-01T01:42:18+09:00 5ヶ月前に退職エントリを出してから、いろんな会社さんだったり個人的にだったり、いろんな人と話させてもらった。 blog.kamipo.net みーみちゃん転職(前)祝いだよ🍶 pic.twitter.com/0kdy47mOiw— Ryuta Kamizono (@kamipo) August 28, 2020 みーみちゃん毎日退職祝いだよ🥩 pic.twitter.com/ZDE8tnRRfD— Ryuta Kamizono (@kamipo) September 1, 2020 今日は会食だよみーみちゃん🌾 pic.twitter.com/myLq9gkpxZ— Ryuta Kamizon… <p>5ヶ月前に退職エントリを出してから、いろんな会社さんだったり個人的にだったり、いろんな人と話させてもらった。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fblog.kamipo.net%2Fentry%2F2020%2F08%2F26%2F145410" title="Treasure Dataを退職します - かみぽわーる" class="embed-card embed-blogcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 190px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://blog.kamipo.net/entry/2020/08/26/145410">blog.kamipo.net</a></cite></p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">みーみちゃん転職(前)祝いだよ🍶 <a href="https://t.co/0kdy47mOiw">pic.twitter.com/0kdy47mOiw</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1299338549519478785?ref_src=twsrc%5Etfw">August 28, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">みーみちゃん毎日退職祝いだよ🥩 <a href="https://t.co/ZDE8tnRRfD">pic.twitter.com/ZDE8tnRRfD</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1300759761130987522?ref_src=twsrc%5Etfw">September 1, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">今日は会食だよみーみちゃん🌾 <a href="https://t.co/myLq9gkpxZ">pic.twitter.com/myLq9gkpxZ</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1306540163842019329?ref_src=twsrc%5Etfw">September 17, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">今日も会食だよみーみちゃん🌀 <a href="https://t.co/d5HTVreV0b">pic.twitter.com/d5HTVreV0b</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1311646378972520449?ref_src=twsrc%5Etfw">October 1, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">今日の会食は蕎麦だよみーみちゃん🍋 <a href="https://t.co/RVbvgPsGPu">pic.twitter.com/RVbvgPsGPu</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1314206015500427266?ref_src=twsrc%5Etfw">October 8, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">今日の会食は寿司だよみーみちゃん🍣🍣 <a href="https://t.co/tPLrbfa08s">pic.twitter.com/tPLrbfa08s</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1316382062924894208?ref_src=twsrc%5Etfw">October 14, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>もともと、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>を改善する活動をずっとしてきて、僕は悲観的なところがあるので、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>を改善することの価値は将来的には下がっていくだろうなと思っていて。なので、常にいまが<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>を改善する最も価値ある瞬間で、だからこそいまそれをやる意義が僕にはあって、いまやらないとその機会を失ってしまうだろうと思って、いまに至っている。</p> <p>とはいえ、僕は価値があると思ってやっている活動もその継続性を考えると、経済的な価値に転換するポイントを見いださないと、僕の資金が続く限りはやります、資金が尽きたら終わりますになってしまうので、これまで"個人の趣味"としてやってきて改善すること以外はマジでどうでもいいと思ってそういうこと何も考えてこなかったから(ぜんぜんどうでもよくなかった)、継続性という点についてはこれからもいろんな人の意見だったりを聞いて考えていきたいところです。</p> <p>まだコロナ禍になるまえ、オフラインでやってたころのESM, Inc.さん主催の<a class="keyword" href="http://d.hatena.ne.jp/keyword/OSS">OSS</a>パッチ会の体験が僕にとってはとてもよく、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>や改善点に関するフィードバックが得られて、それによって僕の活動が誰かにちゃんと届いてるという実感も得られる。</p> <p>その体験をもっと広げられないかという思いもあって、いま、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>のことに関するフィードバックだったり相談だったり(べつに<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>のことじゃなくてもいいし雑談とかでもいい)を受けられるように、SlackのGuestアカウントをもらってる会社さんが何社かある。</p> <p>実際やってみると、あの体験を再現するのはなかなか難しいのだなと感じていて、そもそも、<a class="keyword" href="http://d.hatena.ne.jp/keyword/OSS">OSS</a>パッチ会に来るような人はすでに<a class="keyword" href="http://d.hatena.ne.jp/keyword/OSS">OSS</a>を改善しようって意識を持って集まってる人たちで、そんなみんな毎日「よーし<a class="keyword" href="http://d.hatena.ne.jp/keyword/OSS">OSS</a>を改善するぞ〜!」みたいな感じで日々の業務をしているわけじゃないという、考えてみたらそらそうやなというのがまず最初に感じたこと。</p> <p>あと、関係性的に業務のコードを見れるわけではないので、コード見たら「あ〜そういう感じか〜それは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>側で改善されてくれるとうれしい案件やな〜」ってわかりそうなことも、そういう感度をもった人が相談してくれない限り察知のしようがないというのも感じた。</p> <p>たとえば前職でのケースでいうと、クソクエリでDBが死んでしまうのを<code>MAX_EXECUTION_TIME()</code>で対処したときに、これ<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>使ってたらマジで超絶有用な機能やしBasecamp, Shopify, <a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>も<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>使っとるねんからふつうにみんな必要やろって<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a> 6.0でOptimizer Hintsサポートを入れたり。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F35615" title="Support Optimizer Hints by kamipo · Pull Request #35615 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/35615">github.com</a></cite></p> <p>これ以前にも<code>MAX_EXECUTION_TIME()</code>使いたいねんけどどう思う?って<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%A8%A5%EB%A5%C8%A5%EA%A5%B3">プエルトリコ</a>人の同僚(プランテインが大好き)に相談されたときに、ええと思うけどクエリが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BF%A5%A4%A5%E0%A5%A2%A5%A6%A5%C8">タイムアウト</a>したときのハンドリングしたいよな〜ってことで<code>StatementTimeout</code>エラークラス入れたり。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F31129" title="Add new error class `StatementTimeout` which will be raised when statement timeout exceeded by kamipo · Pull Request #31129 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/31129">github.com</a></cite></p> <p>他にも僕はuniquenessバリデーターの<code>case_sensitive: false</code>警察をやってたんですが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D5%A5%A3%A5%ED%A5%BD%A5%D5%A5%A3%A1%BC%A4%CE%A5%C0%A5%F3%A5%B9">フィロソフィーのダンス</a>のオタクの同僚(おとはす推し)に「<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>側でなんとかしてくださいよ〜」って言われて、既存アプリに影響ある変更をするのは気合い要るけどまあ気合いだけの問題なんでやるか〜ってことでuniquenessバリデーターの挙動変えたり。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F35350" title="Deprecate mismatched collation comparison for uniquness validator by kamipo · Pull Request #35350 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/35350">github.com</a></cite></p> <p>他にもそういう感じのはいっぱいあって、こういう話はコードが見れない側からだとちょっと難しいなと感じた。</p> <p>あとはまあ、「<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>コミッターだけど何か質問ある?(雑談でも可)」って人がいきなり現れても、話題ないよな、僕も自分から雑談するタイプじゃないし、というのも感じてる。</p> <p>そんなこんなで、そういう状況を改善したく、ひとつには僕がいままで自分の活動だったり改善だったりを宣伝してこなかったことも一因だと思っているので、普段から<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>の動向をウォッチしてない人にも僕の活動だったり改善だったりが伝わるように、できるところからということでひとまずブログでアウトプットするのをがんばっていこうというのを今年の抱負としたいと思います。</p> kamipo 165万払って全身脱毛をはじめた hatenablog://entry/26006613684237728 2021-01-28T22:17:16+09:00 2021-01-29T00:10:24+09:00 全身脱毛を11月からはじめてみた。 前職のハイパーサポートエンジニアの同僚が尻の毛を脱毛したエントリを見て、たしかに尻の毛いらんな!と頭の片隅に残っていて、YouTubeでちょいちょいローランドのチャンネルを観ているのでそういえば新宿の新店舗ってどのへんなんやろって調べたらおもいのほか家の近所すぎてテンションあがったのでその日のうちに電話して翌日に全身脱毛の契約をしてしまった。 はてなブログに投稿しました #はてなブログ医療脱毛で全身脱毛に行ってみた - Secret Ninja Bloghttps://t.co/7bIyi20FgG— Toru Takahashi (@nora96o) Se… <p>全身脱毛を11月からはじめてみた。</p> <p>前職のハイパーサポートエンジニアの同僚が尻の毛を脱毛したエントリを見て、たしかに尻の毛いらんな!と頭の片隅に残っていて、<a class="keyword" href="http://d.hatena.ne.jp/keyword/YouTube">YouTube</a>でちょいちょいローランドのチャンネルを観ているのでそういえば新宿の新店舗ってどのへんなんやろって調べたらおもいのほか家の近所すぎてテンションあがったのでその日のうちに電話して翌日に全身脱毛の契約をしてしまった。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr"><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%CF%A4%C6%A4%CA%A5%D6%A5%ED%A5%B0">はてなブログ</a>に投稿しました <a href="https://twitter.com/hashtag/%E3%81%AF%E3%81%A6%E3%81%AA%E3%83%96%E3%83%AD%E3%82%B0?src=hash&amp;ref_src=twsrc%5Etfw">#はてなブログ</a><br>医療脱毛で全身脱毛に行ってみた - Secret Ninja Blog<a href="https://t.co/7bIyi20FgG">https://t.co/7bIyi20FgG</a></p>&mdash; <a class="keyword" href="http://d.hatena.ne.jp/keyword/Toru">Toru</a> Takahashi (@nora96o) <a href="https://twitter.com/nora96o/status/1302603870699319299?ref_src=twsrc%5Etfw">September 6, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>その後、カンジャンケジャンを食いながら今日全身脱毛契約してきてんって話をしたら「医療?美容?」って聞かれて、なるほどそういえばそういうの全然考えてなかったなと思って、もう金払ったあとなんでどうしようもないけど医療脱毛とか美容脱毛について調べてなるほどね〜となったのでその情報をシェアハピします。</p> <h4>医療脱毛と美容脱毛の違い</h4> <p>全身脱毛ってほぼレーザー脱毛なんじゃないかと思うけど、医療(クリニック)と美容(サロン)の違いは、医療のほうが威力の強いレーザーを照射して施術していいので効果が高く、より少ない回数で脱毛の効果が得られるけど威力が強いからめっちゃ痛いらしい。あと、医療のクリニックには医師免許を持った医師が常駐している必要があるらしく、また、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B0%E5%CE%C5%B5%A1%B4%D8">医療機関</a>なので麻酔を使ってもいいとのこと。</p> <p>美容のサロンは逆にレーザーの威力が抑えめなので、効果が得られるのにより回数がかかるけどあんまり痛くないとのこと。あと医師が必要じゃないので開業の敷居が低い。さいきん手越くんやヒカルがオープンした脱毛サロンも、美容脱毛のサロンです。</p> <p>ということで、どっちがいいのかは人それぞれかなというところで、痛みに耐えられるドMで爆速で効果を得たいひとは医療、痛いのは嫌だけど脱毛はしたいというよくばりさんは美容がいいのではないかと思います。</p> <h4>脱毛方法の種類と特徴</h4> <p>じつは8年ほど前にMEN'S TBCでヒゲ脱毛をやったことがあって、それはニードル脱毛だった。ニードル脱毛はおおまかにいうと、毛が生えてる毛穴に針らしきものを差し込んで電気や熱で毛根に直接ダメージを与えて脱毛する方式です。</p> <p>最近の主流はレーザーを照射する方式の脱毛で、おおまかに分けると従来からある熱破壊式と最近流行ってる蓄熱式とがあって、それぞれレーザーによってダメージを与える部位が違う。</p> <p>従来からある熱破壊式は、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%E9%A5%CB%A5%F3">メラニン</a>色素に反応するレーザーで毛根の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E1%A5%E9%A5%CB%A5%F3">メラニン</a>色素に反応させ、その熱で毛根にダメージを与えて脱毛する。一方で最近流行ってる蓄熱式(<a class="keyword" href="http://d.hatena.ne.jp/keyword/SHR">SHR</a>方式)は、毛根より浅いところにあるバルジ領域という発毛因子を持つ部位にダメージを与える方式で、熱破壊式よりも出力の弱いレーザーを連続で照射して脱毛するので、熱破壊式より痛みがマシというのが流行ってる理由だと思う。脱毛効果でいうと、熱破壊式のほうが痛いけど効果は高いとは思う。また、<a class="keyword" href="http://d.hatena.ne.jp/keyword/SHR">SHR</a>方式の脱毛は流行っているけどその理論には懐疑的な意見も結構あって、詳細については君の目で確かめてみてほしい。</p> <p><a href="https://www.is-cl-ginza.com/blog/2020/03/08/0101/">SHR&#x5F0F;&#x8131;&#x6BDB;&#x3092;&#x8A73;&#x3057;&#x304F; | &#x9662;&#x9577;&#x30B3;&#x30E9;&#x30E0;&#xFF5C;&#x9280;&#x5EA7;&#x30A2;&#x30A4;&#x30A8;&#x30B9;&#x30AF;&#x30EA;&#x30CB;&#x30C3;&#x30AF;</a></p> <p><a href="https://www.is-cl-ginza.com/blog/2020/03/13/010/">SHR&#x5F0F;&#x8131;&#x6BDB; &#x30D0;&#x30EB;&#x30B8;&#x9818;&#x57DF;&#x3092;65&#x2103;&#x306B;&#x3059;&#x308B;&#x3068;&#x3044;&#x3046;&#x4E8B; | &#x9662;&#x9577;&#x30B3;&#x30E9;&#x30E0;&#xFF5C;&#x9280;&#x5EA7;&#x30A2;&#x30A4;&#x30A8;&#x30B9;&#x30AF;&#x30EA;&#x30CB;&#x30C3;&#x30AF;</a></p> <h4>美容脱毛は本当に痛くないのか</h4> <p>僕が行ってる<a class="keyword" href="http://d.hatena.ne.jp/keyword/ROLAND">ROLAND</a> Beauty Loungeは美容脱毛なので医療脱毛と比べてどうなのかはわからないけど、基本痛いって感じではないけど一部毛が濃い部位は痛い、あと痛いっていうより熱くて痛いって感じです。だいたい施術のときに「痛み大丈夫ですか?」って聞かれるけど、僕の場合、顔だと鼻下は「痛いっちゃ痛いけどまあ気合いっす」って答えてる感じだけど、Vゾーンの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%AF%A5%B9%A5%AB%A5%EA%A5%D0%A1%BC">エクスカリバー</a>の付け根あたりは「気合いっちゃ気合いだけどクッソ痛いっす」なぐらいには痛いです。</p> <p>ちなみに、MEN'S TBCのニードル脱毛は一回の施術で針を100回ぐらい毛穴に刺すんだけど、その100回1時間ぐらい全部痛いです。</p> <h4>料金</h4> <p>脱毛って "全身脱毛 月3,300円〜" みたいな広告多すぎないですか?ほぼ全部の脱毛の広告や料金表が「で、総額いくらなん?」なのは正直どうなんかと思っている。</p> <p>その点、MEN'S TBCのヒゲ脱毛は1本いくらとかだったんで明瞭会計だったんだけど、3,000本30万ぐらい払って3,000本施術し終わったとき、これ、ヒゲなくなるぐらいまで脱毛しよ思ったら100万ぐらいかかるなと思って追加の契約はしなかった。</p> <p>もうひとつ正直どうなんかと思うところは、たとえば顧客が本当に求めている脱毛効果が10,000本100万ぐらいのときに、いきなり100万っていうたら契約取れなさそうやからとりあえず30万ぐらいから契約取って、あとから何回も追加で契約取るっていうのも、まあ世の中ってそういうもんなんかもしらんけどなんかな〜って感じっすわ。</p> <p>まあそんなこんな思ってるところがあって、<a class="keyword" href="http://d.hatena.ne.jp/keyword/ROLAND">ROLAND</a> Beauty Loungeの初回カウンセリングで予算の相談のときに「効果が感じられるならいくらでもいいですよ」って感じでいったら料金表には載ってないプランなんですけどって最上位プランと最上位一つ下のプランから説明してもらって、値段高すぎて引いたけど最終的に最上位プランで契約した。たぶん2周目の人生だったらどの部位は何回ぐらいでいいとかわかるけど、ヒゲのときのこともあるし、全部位フルベットしといたんであとは全力でいい感じにしてくれるってことでいいんですよねという感じ。</p> <p>まだ施術は5回目なのだけど、サービスには満足してます。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">施術後の不老不死のお茶はうまい🍵 <a href="https://t.co/ZdlpSLncRe">pic.twitter.com/ZdlpSLncRe</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1332232285089792000?ref_src=twsrc%5Etfw">November 27, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <h4>おまけ</h4> <p>全身脱毛契約したあとのカンジャンケジャンです🦀</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">カンジャンケジャンだよみーみちゃん🦀 <a href="https://t.co/UymWIIeeiV">pic.twitter.com/UymWIIeeiV</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1326484191614136320?ref_src=twsrc%5Etfw">November 11, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> kamipo 無職になってからやったこと(保険と給付金) hatenablog://entry/26006613683683642 2021-01-27T16:38:44+09:00 2024-02-10T15:37:10+09:00 無職になってからのこと書こうと思ったら保険と給付金だけで力尽きました。 ハローワークで求職者登録 だいたいの会社員は雇用主によって雇用保険に加入しており、失業中にはいわゆる失業手当を受給できる。 せっかく保険料払っとるねんから一回ぐらい失業手当もらっとかなあかんなということでハローワーク(公共職業安定所)に通ってる。 ちなみに、ハローワークでいうところの "失業" とは、離職中のひとが "就職しようとする意思といつでも就職できる能力があるにもかかわらず職業に就けず、積極的に求職活動を行っている状態にある" ことをいうそうです。 ところで、失業手当がいくらもらえるのかざっと検索するとだいたい賃金… <p>無職になってからのこと書こうと思ったら保険と給付金だけで力尽きました。</p> <h3 id="ハローワークで求職者登録"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%ED%A1%BC%A5%EF%A1%BC%A5%AF">ハローワーク</a>で求職者登録</h3> <p>だいたいの会社員は雇用主によって<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B8%DB%CD%D1%CA%DD%B8%B1">雇用保険</a>に加入しており、失業中にはいわゆる失業手当を受給できる。 せっかく保険料払っとるねんから一回ぐらい失業手当もらっとかなあかんなということで<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%ED%A1%BC%A5%EF%A1%BC%A5%AF">ハローワーク</a>(<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B8%F8%B6%A6%BF%A6%B6%C8%B0%C2%C4%EA%BD%EA">公共職業安定所</a>)に通ってる。</p> <p>ちなみに、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%ED%A1%BC%A5%EF%A1%BC%A5%AF">ハローワーク</a>でいうところの "失業" とは、離職中のひとが "就職しようとする意思といつでも就職できる能力があるにもかかわらず職業に就けず、積極的に求職活動を行っている状態にある" ことをいうそうです。</p> <p>ところで、失業手当がいくらもらえるのかざっと検索するとだいたい賃金の50~80%ぐらいって出てくるんで、え、そんなもらえたら無職のまま豪遊できてしまうで、と思ったけどそんなうまい話はなかった、基本手当日額には上限が存在していて、僕の場合は基本手当日額7,605円であった。</p> <p><a href="https://doda.jp/guide/naiteitaisyoku/koyouhoken/">&#x5931;&#x696D;&#x624B;&#x5F53;&#xFF08;&#x5931;&#x696D;&#x4FDD;&#x967A;&#xFF09;&#x306E;&#x3082;&#x3089;&#x3044;&#x65B9;&#x3084;&#x6761;&#x4EF6;&#x306F;&#xFF1F;&#x624B;&#x7D9A;&#x304D;&#x306F;&#x3069;&#x3046;&#x3059;&#x308B;&#xFF1F;&#x3082;&#x3089;&#x3048;&#x308B;&#x671F;&#x9593;&#x30FB;&#x8A08;&#x7B97;&#x65B9;&#x6CD5;&#x30FB;&#x91D1;&#x984D;&#x3082;&#x89E3;&#x8AAC;&#x3010;&#x793E;&#x52B4;&#x58EB;&#x76E3;&#x4FEE;&#x3011; &#xFF5C;&#x8EE2;&#x8077;&#x306A;&#x3089;doda&#xFF08;&#x30C7;&#x30E5;&#x30FC;&#x30C0;&#xFF09;</a></p> <p><a href="https://funjob.jp/shitsugyokeisan/">https://funjob.jp/shitsugyokeisan/</a></p> <p>また、上記基本手当受給資格がある人が給付日数を1/3以上残して安定した職業に就いた場合に残り日数に応じてもらえる再就職手当というのもある。 僕の場合は所定給付日数180日で、再就職手当もらえるぐらいを目処に働くか〜ぐらいの気持ちでいたけど、気づいたらすでに受給日数120日余裕で超えてて再就職手当チャンスのがしてた…。</p> <p><a href="https://mynavi-job20s.jp/howto/reemployment-allowance.html">再就職手当ってどうやってもらうの?条件・計算方法や申請から受給までの流れを解説|20代・第二新卒・既卒向け転職エージェントのマイナビジョブ20's</a></p> <p>これら失業等給付は言われてみればそうかって感じだけど非課税です。</p> <h3 id="国民年金の免除申請"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%F1%CC%B1%C7%AF%B6%E2">国民年金</a>の免除申請</h3> <p>収入の減少や失業等により<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%F1%CC%B1%C7%AF%B6%E2">国民年金</a>保険料を納めることが経済的に困難な場合、保険料の一部または全額の免除が可能らしいです。</p> <p><a href="https://www.nenkin.go.jp/service/kokunen/menjo/20150428.html">国民年金保険料の免除制度・納付猶予制度|日本年金機構</a></p> <p>とくに経済的にどうということはなかったのだけど、ラーメン屋で麺の量どうしますか(大盛り無料)って言われたときに大盛りでっていう感覚で、とりあえず教えてもらったので免除にしてみた。が、先に<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%ED%A1%BC%A5%EF%A1%BC%A5%AF">ハローワーク</a>で提出してしまっていた<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CE%A5%BF%A6%C9%BC">離職票</a>が必要と言われてめちゃくちゃ面倒だった。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">もうひとつわかったことは、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%F1%CC%B1%C7%AF%B6%E2">国民年金</a>の免除申請には<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CE%A5%BF%A6%C9%BC">離職票</a>が必要だが、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%ED%A1%BC%A5%EF%A1%BC%A5%AF">ハローワーク</a>に行くと<a class="keyword" href="https://d.hatena.ne.jp/keyword/%CE%A5%BF%A6%C9%BC">離職票</a>の原本を取られてしまうので免除申請ができなくなるということ。あなたで今日3人目ですと言われた。ふつうに考えたら<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%ED%A1%BC%A5%EF%A1%BC%A5%AF">ハローワーク</a>から行くやろって感じやのに運用がおかしいと思う。</p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1302858243358691330?ref_src=twsrc%5Etfw">September 7, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">なぜふつうに考えたら<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%CF%A5%ED%A1%BC%A5%EF%A1%BC%A5%AF">ハローワーク</a>から行くやろって感じなのかというと、求職者給付は失業認定が申請日からの起算になるので一日も早く行く<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%BB%A5%F3%A5%C6%A5%A3%A5%D6">インセンティブ</a>があるのに対して、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%F1%CC%B1%C7%AF%B6%E2">国民年金</a>の切替や免除申請は退職日の翌日から<a class="keyword" href="https://d.hatena.ne.jp/keyword/20%C6%FC">20日</a>の猶予があるから。 <a href="https://t.co/TRhErz3I4j">https://t.co/TRhErz3I4j</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1303164020849999872?ref_src=twsrc%5Etfw">September 8, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>面倒だったわりに、免除はしないほうがよかった。まず、年金の保険料は所得控除の対象なのでむしろ払ったほうがよかった。あと、企業型<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B3%CE%C4%EA%B5%F2%BD%D0%C7%AF%B6%E2">確定拠出年金</a>を個人型の<a class="keyword" href="https://d.hatena.ne.jp/keyword/iDeCo">iDeCo</a>口座に移管するのに、免除中は移管の手続きができないということ。というか、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BF%B7%BD%C9%B6%E8%CC%F2%BD%EA">新宿区役所</a>で<a class="keyword" href="https://d.hatena.ne.jp/keyword/iDeCo">iDeCo</a>のこと聞こうとしたら<a class="keyword" href="https://d.hatena.ne.jp/keyword/iDeCo">iDeCo</a>はここじゃないですって対応されたけど、年<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B6%E2%CA%DD">金保</a>険料の免除を勧めるんやったら<a class="keyword" href="https://d.hatena.ne.jp/keyword/iDeCo">iDeCo</a>への移管ができなくなる副作用が存在するねんから関係なくはないし、そこの対応は改善してほしいと思いました。</p> <p>ちなみに、年<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B6%E2%CA%DD">金保</a>険料の納付書はあるので、ワンチャン支払いしたら免除解除されんかなと思ってやってみたけど、年<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B6%E2%CA%DD">金保</a>険料過誤納額還付・充当通知書が届いて余計面倒なことになっただけなのでおすすめしません。</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">年金免除申請してると<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B4%EB%B6%C8%C7%AF%B6%E2">企業年金</a>の<a class="keyword" href="https://d.hatena.ne.jp/keyword/iDeCo">iDeCo</a>への移管ができないからとりあえず年金払ってみたけどやはり免除取消しの申請をせねばならぬのか… <a href="https://t.co/XKCCt3dwge">pic.twitter.com/XKCCt3dwge</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1330880920513957889?ref_src=twsrc%5Etfw">November 23, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <h4 id="健康保険の任意継続">健康保険の任意継続</h4> <p>任意継続とは、退職後も前職の健康保険の制度に引き続き加入できる制度です。福利厚生などもあると思うけど、前職が加入してる組合の健康保険を任意継続するか<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%F1%CC%B1%B7%F2%B9%AF%CA%DD%B8%B1">国民健康保険</a>に加入するかは単に保険料の額で決めてよいと言われたので、<a class="keyword" href="https://d.hatena.ne.jp/keyword/%BF%B7%BD%C9%B6%E8%CC%F2%BD%EA">新宿区役所</a>で保険料がいくらになりそうか聞いてみた。自力で計算してみてもあってるのかどうかわからんかったので。</p> <p><a href="https://www.city.shinjuku.lg.jp/hoken/hoken01_001028.html">&#x4FDD;&#x967A;&#x6599;&#x306E;&#x8A08;&#x7B97;&#x65B9;&#x6CD5;&#x306B;&#x3064;&#x3044;&#x3066;&#xFF1A;&#x65B0;&#x5BBF;&#x533A;</a></p> <p><a href="https://www.city.shinjuku.lg.jp/hoken/hoken01_002030.html">&#x4FDD;&#x967A;&#x6599;&#x306E;&#x6E1B;&#x514D;&#x306B;&#x3064;&#x3044;&#x3066;&#xFF1A;&#x65B0;&#x5BBF;&#x533A;</a></p> <p><a href="https://www.its-kenpo.or.jp/hoken/nini/about/hutan.html">https://www.its-kenpo.or.jp/hoken/nini/about/hutan.html</a></p> <p>結果、任意継続のほうが保険料は安かったけど、手違いで勧められるままに記入した紙が<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%F1%CC%B1%B7%F2%B9%AF%CA%DD%B8%B1">国民健康保険</a>の加入手続きの用紙で、倍ぐらい保険料が高いほうに加入手続きが完了してしまって泣いた🥲</p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr"><a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%F1%CA%DD">国保</a>切替か任意継続のどちらか安いほうを選べとの教えを賜っているけど、調べた限り<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%F1%CA%DD">国保</a>の年間保険料を減額する裏技でもない限り僕の推定算定基礎額に保険料率を掛けたらほぼ間違いなく賦課限度額に達するから任意継続より<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%F1%CA%DD">国保</a>切替のほうが安くなるわけがないと思うけどそれならそう教えを賜るはずで謎</p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1302495201022210048?ref_src=twsrc%5Etfw">September 6, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">ふつうの人なら<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%F1%CA%DD">国保</a>のほうが安くなった可能性あることはわかったけど僕の場合はどっちにしろ保険料<a class="keyword" href="https://d.hatena.ne.jp/keyword/%A5%AB%A5%F3%A5%B9%A5%C8">カンスト</a>だったので任意継続にしますって言ったのに手違いで<a class="keyword" href="https://d.hatena.ne.jp/keyword/%B9%F1%CA%DD">国保</a>に加入の手続きが完了してしまって任意継続した保険証が手に入ったあと喪失の手続きを取らないといけない事態になってしまった…😨</p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1302854959176663041?ref_src=twsrc%5Etfw">September 7, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p>保険と給付金以外のこともそのうち書くかもしれません。</p> kamipo Rails 6.1で `created_at > ?` みたいなクエリをいい感じに生成する hatenablog://entry/26006613678267497 2021-01-14T19:17:53+09:00 2021-01-15T06:08:46+09:00 Rails 6.1の目玉機能として以下のように書けるwhere拡張を入れてたんですが、いろいろあって6.1からはrevertされてしまいました🥲 posts = Post.order(:id) posts.where("id >": 9).pluck(:id) # => [10, 11] posts.where("id >=": 9).pluck(:id) # => [9, 10, 11] posts.where("id <": 3).pluck(:id) # => [1, 2] posts.where("id <=": 3).pluck(:id) # => [1, 2, 3] github.… <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a> 6.1の目玉機能として以下のように書けるwhere拡張を入れてたんですが、いろいろあって6.1からはrevertされてしまいました🥲</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink>posts = <span class="synType">Post</span>.order(<span class="synConstant">:id</span>) posts.where(<span class="synSpecial">&quot;</span><span class="synConstant">id &gt;</span><span class="synSpecial">&quot;</span>: <span class="synConstant">9</span>).pluck(<span class="synConstant">:id</span>) <span class="synComment"># =&gt; [10, 11]</span> posts.where(<span class="synSpecial">&quot;</span><span class="synConstant">id &gt;=</span><span class="synSpecial">&quot;</span>: <span class="synConstant">9</span>).pluck(<span class="synConstant">:id</span>) <span class="synComment"># =&gt; [9, 10, 11]</span> posts.where(<span class="synSpecial">&quot;</span><span class="synConstant">id &lt;</span><span class="synSpecial">&quot;</span>: <span class="synConstant">3</span>).pluck(<span class="synConstant">:id</span>) <span class="synComment"># =&gt; [1, 2]</span> posts.where(<span class="synSpecial">&quot;</span><span class="synConstant">id &lt;=</span><span class="synSpecial">&quot;</span>: <span class="synConstant">3</span>).pluck(<span class="synConstant">:id</span>) <span class="synComment"># =&gt; [1, 2, 3]</span> </pre> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F39613" title="Support `where` with comparison operators (`&gt;`, `&gt;=`, `&lt;`, and `&lt;=`) by kamipo · Pull Request #39613 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/39613">github.com</a></cite></p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F39863" title="Support `where` with comparison operators (`&gt;`, `&gt;=`, `&lt;`, and `&lt;=`) Take 2 by kamipo · Pull Request #39863 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/39863">github.com</a></cite></p> <p>なんですが、そんなことで引き下がる僕ではないので、6.1ではpredicate生成に干渉できる拡張ポイントを用意しており、以下のようなコードを適当に読み込まれるところにしたためておけば、いともたやすくwhere拡張を実現することができます。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synType">ActiveSupport</span>.on_load(<span class="synConstant">:active_record</span>) <span class="synStatement">do</span> <span class="synType">ActiveRecord</span>::<span class="synType">PredicateBuilder</span>.prepend <span class="synType">Module</span>.new { <span class="synPreProc">def</span> <span class="synIdentifier">[]</span>(attr_name, value, operator = <span class="synConstant">nil</span>) <span class="synStatement">if</span> !operator &amp;&amp; attr_name.end_with?(<span class="synSpecial">&quot;</span><span class="synConstant">&gt;</span><span class="synSpecial">&quot;</span>, <span class="synSpecial">&quot;</span><span class="synConstant">&gt;=</span><span class="synSpecial">&quot;</span>, <span class="synSpecial">&quot;</span><span class="synConstant">&lt;</span><span class="synSpecial">&quot;</span>, <span class="synSpecial">&quot;</span><span class="synConstant">&lt;=</span><span class="synSpecial">&quot;</span>) <span class="synSpecial">/\A(?&lt;attr_name&gt;.+?)\s*(?&lt;operator&gt;</span><span class="synConstant">&gt;</span><span class="synSpecial">|</span><span class="synConstant">&gt;=</span><span class="synSpecial">|</span><span class="synConstant">&lt;</span><span class="synSpecial">|</span><span class="synConstant">&lt;=</span><span class="synSpecial">)\z/</span> =~ attr_name operator = <span class="synType">OPERATORS</span>[operator] <span class="synStatement">end</span> <span class="synStatement">super</span> <span class="synPreProc">end</span> <span class="synType">OPERATORS</span> = { <span class="synSpecial">&quot;</span><span class="synConstant">&gt;</span><span class="synSpecial">&quot;</span> =&gt; <span class="synConstant">:gt</span>, <span class="synSpecial">&quot;</span><span class="synConstant">&gt;=</span><span class="synSpecial">&quot;</span> =&gt; <span class="synConstant">:gteq</span>, <span class="synSpecial">&quot;</span><span class="synConstant">&lt;</span><span class="synSpecial">&quot;</span> =&gt; <span class="synConstant">:lt</span>, <span class="synSpecial">&quot;</span><span class="synConstant">&lt;=</span><span class="synSpecial">&quot;</span> =&gt; <span class="synConstant">:lteq</span> }.freeze } <span class="synStatement">end</span> </pre> <p>ぜひ活用してくださいね😉</p> kamipo activerecord-importを利用して無効なデータを無理やりINSERTする hatenablog://entry/26006613674860323 2021-01-06T14:44:07+09:00 2021-01-06T14:44:07+09:00 activerecord-importと:on_duplicate_key_ignoreオプションを組み合わせるとカラム定義の範囲外の値であっても無理やりINSERTすることができます。 # frozen_string_literal: true require "bundler/inline" gemfile(true) do source "https://rubygems.org" git_source(:github) { |repo| "https://github.com/#{repo}.git" } gem "activerecord", "6.1.0" gem "activer… <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/activerecord">activerecord</a>-importと<code>:on_duplicate_key_ignore</code>オプションを組み合わせるとカラム定義の範囲外の値であっても無理やりINSERTすることができます。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synComment"># frozen_string_literal: true</span> <span class="synPreProc">require</span> <span class="synSpecial">&quot;</span><span class="synConstant">bundler/inline</span><span class="synSpecial">&quot;</span> gemfile(<span class="synConstant">true</span>) <span class="synStatement">do</span> source <span class="synSpecial">&quot;</span><span class="synConstant">https://rubygems.org</span><span class="synSpecial">&quot;</span> git_source(<span class="synConstant">:github</span>) { |<span class="synIdentifier">repo</span>| <span class="synSpecial">&quot;</span><span class="synConstant">https://github.com/</span><span class="synSpecial">#{</span>repo<span class="synSpecial">}</span><span class="synConstant">.git</span><span class="synSpecial">&quot;</span> } gem <span class="synSpecial">&quot;</span><span class="synConstant">activerecord</span><span class="synSpecial">&quot;</span>, <span class="synSpecial">&quot;</span><span class="synConstant">6.1.0</span><span class="synSpecial">&quot;</span> gem <span class="synSpecial">&quot;</span><span class="synConstant">activerecord-import</span><span class="synSpecial">&quot;</span> gem <span class="synSpecial">&quot;</span><span class="synConstant">mysql2</span><span class="synSpecial">&quot;</span> <span class="synStatement">end</span> <span class="synPreProc">require</span> <span class="synSpecial">&quot;</span><span class="synConstant">active_record</span><span class="synSpecial">&quot;</span> <span class="synPreProc">require</span> <span class="synSpecial">&quot;</span><span class="synConstant">logger</span><span class="synSpecial">&quot;</span> <span class="synType">ActiveRecord</span>::<span class="synType">Base</span>.establish_connection(<span class="synConstant">adapter</span>: <span class="synSpecial">&quot;</span><span class="synConstant">mysql2</span><span class="synSpecial">&quot;</span>, <span class="synConstant">database</span>: <span class="synSpecial">&quot;</span><span class="synConstant">test</span><span class="synSpecial">&quot;</span>, <span class="synConstant">username</span>: <span class="synSpecial">&quot;</span><span class="synConstant">root</span><span class="synSpecial">&quot;</span>) <span class="synType">ActiveRecord</span>::<span class="synType">Base</span>.logger = <span class="synType">Logger</span>.new(<span class="synIdentifier">STDOUT</span>) <span class="synType">ActiveRecord</span>::<span class="synType">Schema</span>.define <span class="synStatement">do</span> create_table <span class="synConstant">:users</span>, <span class="synConstant">force</span>: <span class="synConstant">true</span> <span class="synStatement">do</span> |<span class="synIdentifier">t</span>| t.string <span class="synConstant">:name</span>, <span class="synConstant">index</span>: { <span class="synConstant">unique</span>: <span class="synConstant">true</span> } t.decimal <span class="synConstant">:money</span>, <span class="synConstant">precision</span>: <span class="synConstant">10</span> <span class="synStatement">end</span> <span class="synStatement">end</span> <span class="synPreProc">class</span> <span class="synType">User</span> &lt; <span class="synType">ActiveRecord</span>::<span class="synType">Base</span> <span class="synPreProc">end</span> attributes = [ { <span class="synConstant">name</span>: <span class="synSpecial">&quot;</span><span class="synConstant">foo</span><span class="synSpecial">&quot;</span>, <span class="synConstant">money</span>: <span class="synSpecial">&quot;</span><span class="synConstant">10000000000</span><span class="synSpecial">&quot;</span> }, { <span class="synConstant">name</span>: <span class="synSpecial">&quot;</span><span class="synConstant">foo</span><span class="synSpecial">&quot;</span>, <span class="synConstant">money</span>: <span class="synSpecial">&quot;</span><span class="synConstant">20000000000</span><span class="synSpecial">&quot;</span> }, ] <span class="synType">User</span>.import(attributes, <span class="synConstant">on_duplicate_key_ignore</span>: <span class="synConstant">true</span>) <span class="synComment"># User.insert_all(attributes)</span> puts puts <span class="synType">User</span>.pluck(<span class="synConstant">:money</span>) <span class="synComment"># =&gt; 9999999999</span> puts </pre> <pre class="code" data-lang="" data-unlink>% ruby foo.rb Fetching gem metadata from https://rubygems.org/.............. Fetching gem metadata from https://rubygems.org/. Resolving dependencies... Using concurrent-ruby 1.1.7 Using bundler 2.2.3 Using minitest 5.14.3 Using zeitwerk 2.4.2 Using mysql2 0.5.3 Using i18n 1.8.7 Using tzinfo 2.0.4 Using activesupport 6.1.0 Using activemodel 6.1.0 Using activerecord 6.1.0 Using activerecord-import 1.0.7 -- create_table(:users, {:force=&gt;true}) D, [2021-01-06T14:04:10.296375 #81282] DEBUG -- : (66.5ms) DROP TABLE IF EXISTS `users` D, [2021-01-06T14:04:10.377990 #81282] DEBUG -- : (80.7ms) CREATE TABLE `users` (`id` bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` varchar(255), `money` decimal(10), UNIQUE INDEX `index_users_on_name` (`name`)) -&gt; 0.1967s D, [2021-01-06T14:04:10.397652 #81282] DEBUG -- : ActiveRecord::InternalMetadata Load (0.5ms) SELECT `ar_internal_metadata`.* FROM `ar_internal_metadata` WHERE `ar_internal_metadata`.`key` = &#39;environment&#39; LIMIT 1 D, [2021-01-06T14:04:10.417055 #81282] DEBUG -- : (0.3ms) SELECT @@max_allowed_packet D, [2021-01-06T14:04:10.424171 #81282] DEBUG -- : User Create Many (6.8ms) INSERT IGNORE INTO `users` (`name`,`money`) VALUES (&#39;foo&#39;,10000000000),(&#39;foo&#39;,20000000000) D, [2021-01-06T14:04:10.428391 #81282] DEBUG -- : (3.4ms) SELECT `users`.`money` FROM `users` 9999999999</pre> <p><a href="https://gist.github.com/kamipo/3db82c3bb7cbcbf007b4d4367a5c5227">https://gist.github.com/kamipo/3db82c3bb7cbcbf007b4d4367a5c5227</a></p> <p>これはどういう原理かというと、<a class="keyword" href="http://d.hatena.ne.jp/keyword/activerecord">activerecord</a>-importではINSERTしたいけどすでに(ユニークキーが)おなじレコードがあるときはスルーしたい(i.e. <a class="keyword" href="http://d.hatena.ne.jp/keyword/on_">on_</a>duplicate_key_ignore)という機能を実現するのに<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>ではINSERT IGNORE構文を使っていて、INSERT IGNOREではINSERT中のすべてのエラーを無視して無効な値は可能ならもっとも近い値に調整してINSERTするという振る舞いをするため、このような挙動を引き起こすことができます。</p> <p>では、INSERTしたいけどすでに(ユニークキーが)おなじレコードがあるときはスルーしたい、けど無効な値はちゃんとエラーにしてほしいときはどうしたらいいかというと、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a> 6.0から使える<code>insert_all</code>というバルクインサート用の<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>を使うことができます。<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>チームではわいが<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>チョットデキルので、この問題についてはレビューでフィードバックして対処されており安心してご利用になることができます。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F35077%23discussion_r256691968" title="Add insert_many to ActiveRecord models by boblail · Pull Request #35077 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/35077#discussion_r256691968">github.com</a></cite></p> <p>See also</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fsongmu.jp%2Friji%2Fentry%2F2015-07-20-insert-ignore.html" title="kamipo TRADITIONALでは防げないINSERT IGNOREという名の化け物 | おそらくはそれさえも平凡な日々" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://songmu.jp/riji/entry/2015-07-20-insert-ignore.html">songmu.jp</a></cite></p> <p>それでは本年も引き続きよろしくおねがいいたします。</p> kamipo create_or_find_byでcreateもfind_byも失敗させる hatenablog://entry/26006613666057814 2020-12-16T19:07:23+09:00 2020-12-22T08:01:26+09:00 Active Recordの話です。 create_or_find_byの実装はcreateしてみてユニーク制約に引っかかったらfind_byしてみるなので、ふつうに考えるとfind_byは成功しそうに見えます。 def create_or_find_by(attributes, &block) transaction(requires_new: true) { create(attributes, &block) } rescue ActiveRecord::RecordNotUnique find_by!(attributes) end ですが、以下のスクリプトを実行するとcreate_o… <p>Active Recordの話です。</p> <p>create_or_find_byの実装はcreateしてみてユニーク制約に引っかかったらfind_byしてみるなので、ふつうに考えるとfind_byは成功しそうに見えます。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink> <span class="synPreProc">def</span> <span class="synIdentifier">create_or_find_by</span>(attributes, &amp;block) transaction(<span class="synConstant">requires_new</span>: <span class="synConstant">true</span>) { create(attributes, &amp;block) } <span class="synPreProc">rescue</span> <span class="synType">ActiveRecord</span>::<span class="synType">RecordNotUnique</span> find_by!(attributes) <span class="synPreProc">end</span> </pre> <p>ですが、以下の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%B9%A5%AF%A5%EA%A5%D7%A5%C8">スクリプト</a>を実行するとcreate_or_find_byはcreateがRecordNotUnique例外を吐いたあと、find_byもRecordNotFound例外を吐いてレコードを見つけられずに死にます。</p> <p><del>ちょっと今から会食なので原理は帰ってから書き足しますしばしお待ちを🙇‍♂️</del></p> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">これ時間あったら続き書きたいけどとりいそぎ説明すると、REPEATABLE READでファントムリードを防いでることを利用して失敗させてるからREAD COMMITTEDにしてファントムリードを許すかSERIALIZABLEにするかLocking Readして別<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>の更新をブロックすると失敗を防げます。 <a href="https://t.co/v4LBhwDWeR">https://t.co/v4LBhwDWeR</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1340598380641144833?ref_src=twsrc%5Etfw">December 20, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> </p> <p><a href="https://github.com/rails/rails/blob/3e5d504f7828118928bfcc08e5be284775836794/activerecord/lib/active_record/relation.rb#L208-L212">https://github.com/rails/rails/blob/3e5d504f7828118928bfcc08e5be284775836794/activerecord/lib/active_record/relation.rb#L208-L212</a></p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synComment"># frozen_string_literal: true</span> <span class="synPreProc">require</span> <span class="synSpecial">&quot;</span><span class="synConstant">bundler/inline</span><span class="synSpecial">&quot;</span> gemfile(<span class="synConstant">true</span>) <span class="synStatement">do</span> source <span class="synSpecial">&quot;</span><span class="synConstant">https://rubygems.org</span><span class="synSpecial">&quot;</span> git_source(<span class="synConstant">:github</span>) { |<span class="synIdentifier">repo</span>| <span class="synSpecial">&quot;</span><span class="synConstant">https://github.com/</span><span class="synSpecial">#{</span>repo<span class="synSpecial">}</span><span class="synConstant">.git</span><span class="synSpecial">&quot;</span> } gem <span class="synSpecial">&quot;</span><span class="synConstant">activerecord</span><span class="synSpecial">&quot;</span>, <span class="synSpecial">&quot;</span><span class="synConstant">6.1.0</span><span class="synSpecial">&quot;</span> gem <span class="synSpecial">&quot;</span><span class="synConstant">mysql2</span><span class="synSpecial">&quot;</span> <span class="synStatement">end</span> <span class="synPreProc">require</span> <span class="synSpecial">&quot;</span><span class="synConstant">active_record</span><span class="synSpecial">&quot;</span> <span class="synPreProc">require</span> <span class="synSpecial">&quot;</span><span class="synConstant">logger</span><span class="synSpecial">&quot;</span> <span class="synType">ActiveRecord</span>::<span class="synType">Base</span>.establish_connection(<span class="synConstant">adapter</span>: <span class="synSpecial">&quot;</span><span class="synConstant">mysql2</span><span class="synSpecial">&quot;</span>, <span class="synConstant">database</span>: <span class="synSpecial">&quot;</span><span class="synConstant">test</span><span class="synSpecial">&quot;</span>, <span class="synConstant">username</span>: <span class="synSpecial">&quot;</span><span class="synConstant">root</span><span class="synSpecial">&quot;</span>) <span class="synType">ActiveRecord</span>::<span class="synType">Base</span>.logger = <span class="synType">Logger</span>.new(<span class="synIdentifier">STDOUT</span>) <span class="synType">ActiveRecord</span>::<span class="synType">Schema</span>.define <span class="synStatement">do</span> create_table <span class="synConstant">:users</span>, <span class="synConstant">force</span>: <span class="synConstant">true</span> <span class="synStatement">do</span> |<span class="synIdentifier">t</span>| t.string <span class="synConstant">:name</span>, <span class="synConstant">index</span>: { <span class="synConstant">unique</span>: <span class="synConstant">true</span> } <span class="synStatement">end</span> <span class="synStatement">end</span> <span class="synPreProc">class</span> <span class="synType">User</span> &lt; <span class="synType">ActiveRecord</span>::<span class="synType">Base</span> <span class="synPreProc">end</span> t = <span class="synType">Thread</span>.new <span class="synStatement">do</span> sleep <span class="synConstant">0.1</span> <span class="synType">User</span>.create!(<span class="synConstant">name</span>: <span class="synSpecial">&quot;</span><span class="synConstant">foo</span><span class="synSpecial">&quot;</span>) <span class="synStatement">end</span> <span class="synType">User</span>.transaction <span class="synStatement">do</span> <span class="synType">User</span>.find_by!(<span class="synConstant">name</span>: <span class="synSpecial">&quot;</span><span class="synConstant">foo</span><span class="synSpecial">&quot;</span>) <span class="synStatement">rescue</span> <span class="synType">ActiveRecord</span>::<span class="synType">RecordNotFound</span> puts <span class="synSpecial">'</span><span class="synConstant">User&lt;name: &quot;foo&quot;&gt; not found</span><span class="synSpecial">'</span> sleep <span class="synConstant">0.2</span> <span class="synType">User</span>.create_or_find_by!(<span class="synConstant">name</span>: <span class="synSpecial">&quot;</span><span class="synConstant">foo</span><span class="synSpecial">&quot;</span>).tap <span class="synStatement">do</span> puts <span class="synSpecial">'</span><span class="synConstant">User&lt;name: &quot;foo&quot;&gt; has found or created</span><span class="synSpecial">'</span> <span class="synStatement">end</span> <span class="synStatement">end</span> t.join </pre> <pre class="code" data-lang="" data-unlink>% ruby foo.rb Fetching gem metadata from https://rubygems.org/......... Resolving dependencies... Using concurrent-ruby 1.1.7 Using i18n 1.8.5 Using minitest 5.14.2 Using tzinfo 2.0.3 Using zeitwerk 2.4.2 Using activesupport 6.1.0 Using activemodel 6.1.0 Using activerecord 6.1.0 Using bundler 2.1.4 Using mysql2 0.5.3 -- create_table(:users, {:force=&gt;true}) D, [2020-12-16T18:55:22.915787 #49715] DEBUG -- : (12.2ms) DROP TABLE IF EXISTS `users` D, [2020-12-16T18:55:22.954341 #49715] DEBUG -- : (37.5ms) CREATE TABLE `users` (`id` bigint NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` varchar(255), UNIQUE INDEX `index_users_on_name` (`name`)) -&gt; 0.0754s D, [2020-12-16T18:55:23.132151 #49715] DEBUG -- : ActiveRecord::InternalMetadata Load (0.7ms) SELECT `ar_internal_metadata`.* FROM `ar_internal_metadata` WHERE `ar_internal_metadata`.`key` = &#39;environment&#39; LIMIT 1 D, [2020-12-16T18:55:23.147310 #49715] DEBUG -- : TRANSACTION (0.3ms) BEGIN D, [2020-12-16T18:55:23.151778 #49715] DEBUG -- : User Load (0.8ms) SELECT `users`.* FROM `users` WHERE `users`.`name` = &#39;foo&#39; LIMIT 1 User&lt;name: &#34;foo&#34;&gt; not found D, [2020-12-16T18:55:23.283424 #49715] DEBUG -- : TRANSACTION (0.3ms) BEGIN D, [2020-12-16T18:55:23.287159 #49715] DEBUG -- : User Create (3.5ms) INSERT INTO `users` (`name`) VALUES (&#39;foo&#39;) D, [2020-12-16T18:55:23.290553 #49715] DEBUG -- : TRANSACTION (2.8ms) COMMIT D, [2020-12-16T18:55:23.359126 #49715] DEBUG -- : TRANSACTION (0.3ms) SAVEPOINT active_record_1 D, [2020-12-16T18:55:23.360585 #49715] DEBUG -- : User Create (1.2ms) INSERT INTO `users` (`name`) VALUES (&#39;foo&#39;) D, [2020-12-16T18:55:23.361383 #49715] DEBUG -- : TRANSACTION (0.3ms) ROLLBACK TO SAVEPOINT active_record_1 D, [2020-12-16T18:55:23.362859 #49715] DEBUG -- : User Load (0.5ms) SELECT `users`.* FROM `users` WHERE `users`.`name` = &#39;foo&#39; LIMIT 1 D, [2020-12-16T18:55:23.363994 #49715] DEBUG -- : TRANSACTION (0.5ms) ROLLBACK Traceback (most recent call last): 12: from foo.rb:34:in `&lt;main&gt;&#39; 11: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/transactions.rb:209:in `transaction&#39; 10: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:320:in `transaction&#39; 9: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/transaction.rb:308:in `within_new_transaction&#39; 8: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `synchronize&#39; 7: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `handle_interrupt&#39; 6: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `block in synchronize&#39; 5: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `handle_interrupt&#39; 4: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:26:in `block (2 levels) in synchronize&#39; 3: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/transaction.rb:310:in `block in within_new_transaction&#39; 2: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:320:in `block in transaction&#39; 1: from foo.rb:35:in `block in &lt;main&gt;&#39; /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/core.rb:366:in `find_by!&#39;: Couldn&#39;t find User (ActiveRecord::RecordNotFound) 87: from foo.rb:34:in `&lt;main&gt;&#39; 86: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/transactions.rb:209:in `transaction&#39; 85: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:320:in `transaction&#39; 84: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/transaction.rb:308:in `within_new_transaction&#39; 83: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `synchronize&#39; 82: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `handle_interrupt&#39; 81: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `block in synchronize&#39; 80: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `handle_interrupt&#39; 79: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:26:in `block (2 levels) in synchronize&#39; 78: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/transaction.rb:310:in `block in within_new_transaction&#39; 77: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:320:in `block in transaction&#39; 76: from foo.rb:35:in `block in &lt;main&gt;&#39; 75: from foo.rb:41:in `rescue in block in &lt;main&gt;&#39; 74: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/querying.rb:22:in `create_or_find_by!&#39; 73: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:218:in `create_or_find_by!&#39; 72: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation/delegation.rb:108:in `method_missing&#39; 71: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:406:in `scoping&#39; 70: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:811:in `_scoping&#39; 69: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:406:in `block in scoping&#39; 68: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation/delegation.rb:108:in `block in method_missing&#39; 67: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation/delegation.rb:108:in `public_send&#39; 66: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/transactions.rb:209:in `transaction&#39; 65: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:320:in `transaction&#39; 64: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/transaction.rb:308:in `within_new_transaction&#39; 63: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `synchronize&#39; 62: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `handle_interrupt&#39; 61: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `block in synchronize&#39; 60: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `handle_interrupt&#39; 59: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:26:in `block (2 levels) in synchronize&#39; 58: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/transaction.rb:310:in `block in within_new_transaction&#39; 57: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:320:in `block in transaction&#39; 56: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:218:in `block in create_or_find_by!&#39; 55: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:114:in `create!&#39; 54: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:406:in `scoping&#39; 53: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:811:in `_scoping&#39; 52: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:406:in `block in scoping&#39; 51: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:114:in `block in create!&#39; 50: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:806:in `_create!&#39; 49: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/persistence.rb:55:in `create!&#39; 48: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/suppressor.rb:48:in `save!&#39; 47: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/transactions.rb:300:in `save!&#39; 46: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/transactions.rb:348:in `with_transaction_returning_status&#39; 45: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:318:in `transaction&#39; 44: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/transactions.rb:352:in `block in with_transaction_returning_status&#39; 43: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/transactions.rb:300:in `block in save!&#39; 42: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/validations.rb:53:in `save!&#39; 41: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/persistence.rb:507:in `save!&#39; 40: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/timestamp.rb:126:in `create_or_update&#39; 39: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/callbacks.rb:457:in `create_or_update&#39; 38: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/callbacks.rb:824:in `_run_save_callbacks&#39; 37: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/callbacks.rb:98:in `run_callbacks&#39; 36: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/callbacks.rb:457:in `block in create_or_update&#39; 35: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/persistence.rb:900:in `create_or_update&#39; 34: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/timestamp.rb:108:in `_create_record&#39; 33: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/callbacks.rb:461:in `_create_record&#39; 32: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/callbacks.rb:824:in `_run_create_callbacks&#39; 31: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/callbacks.rb:98:in `run_callbacks&#39; 30: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/callbacks.rb:461:in `block in _create_record&#39; 29: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/attribute_methods/dirty.rb:201:in `_create_record&#39; 28: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/locking/optimistic.rb:79:in `_create_record&#39; 27: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/counter_cache.rb:166:in `_create_record&#39; 26: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/persistence.rb:929:in `_create_record&#39; 25: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/persistence.rb:375:in `_insert_record&#39; 24: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/query_cache.rb:22:in `insert&#39; 23: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:171:in `insert&#39; 22: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:136:in `exec_insert&#39; 21: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/mysql/database_statements.rb:55:in `exec_query&#39; 20: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:215:in `execute_and_free&#39; 19: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/mysql/database_statements.rb:50:in `execute&#39; 18: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:204:in `execute&#39; 17: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract_adapter.rb:688:in `log&#39; 16: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/notifications/instrumenter.rb:24:in `instrument&#39; 15: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract_adapter.rb:696:in `block in log&#39; 14: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `synchronize&#39; 13: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `handle_interrupt&#39; 12: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `block in synchronize&#39; 11: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `handle_interrupt&#39; 10: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:26:in `block (2 levels) in synchronize&#39; 9: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract_adapter.rb:697:in `block (2 levels) in log&#39; 8: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:205:in `block in execute&#39; 7: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/dependencies/interlock.rb:47:in `permit_concurrent_loads&#39; 6: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/share_lock.rb:187:in `yield_shares&#39; 5: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/dependencies/interlock.rb:48:in `block in permit_concurrent_loads&#39; 4: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:206:in `block (2 levels) in execute&#39; 3: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/mysql2-0.5.3/lib/mysql2/client.rb:130:in `query&#39; 2: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/mysql2-0.5.3/lib/mysql2/client.rb:130:in `handle_interrupt&#39; 1: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/mysql2-0.5.3/lib/mysql2/client.rb:131:in `block in query&#39; /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/mysql2-0.5.3/lib/mysql2/client.rb:131:in `_query&#39;: Duplicate entry &#39;foo&#39; for key &#39;users.index_users_on_name&#39; (Mysql2::Error) 87: from foo.rb:34:in `&lt;main&gt;&#39; 86: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/transactions.rb:209:in `transaction&#39; 85: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:320:in `transaction&#39; 84: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/transaction.rb:308:in `within_new_transaction&#39; 83: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `synchronize&#39; 82: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `handle_interrupt&#39; 81: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `block in synchronize&#39; 80: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `handle_interrupt&#39; 79: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:26:in `block (2 levels) in synchronize&#39; 78: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/transaction.rb:310:in `block in within_new_transaction&#39; 77: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:320:in `block in transaction&#39; 76: from foo.rb:35:in `block in &lt;main&gt;&#39; 75: from foo.rb:41:in `rescue in block in &lt;main&gt;&#39; 74: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/querying.rb:22:in `create_or_find_by!&#39; 73: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:218:in `create_or_find_by!&#39; 72: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation/delegation.rb:108:in `method_missing&#39; 71: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:406:in `scoping&#39; 70: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:811:in `_scoping&#39; 69: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:406:in `block in scoping&#39; 68: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation/delegation.rb:108:in `block in method_missing&#39; 67: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation/delegation.rb:108:in `public_send&#39; 66: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/transactions.rb:209:in `transaction&#39; 65: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:320:in `transaction&#39; 64: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/transaction.rb:308:in `within_new_transaction&#39; 63: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `synchronize&#39; 62: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `handle_interrupt&#39; 61: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `block in synchronize&#39; 60: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `handle_interrupt&#39; 59: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:26:in `block (2 levels) in synchronize&#39; 58: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/transaction.rb:310:in `block in within_new_transaction&#39; 57: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:320:in `block in transaction&#39; 56: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:218:in `block in create_or_find_by!&#39; 55: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:114:in `create!&#39; 54: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:406:in `scoping&#39; 53: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:811:in `_scoping&#39; 52: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:406:in `block in scoping&#39; 51: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:114:in `block in create!&#39; 50: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:806:in `_create!&#39; 49: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/persistence.rb:55:in `create!&#39; 48: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/suppressor.rb:48:in `save!&#39; 47: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/transactions.rb:300:in `save!&#39; 46: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/transactions.rb:348:in `with_transaction_returning_status&#39; 45: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:318:in `transaction&#39; 44: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/transactions.rb:352:in `block in with_transaction_returning_status&#39; 43: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/transactions.rb:300:in `block in save!&#39; 42: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/validations.rb:53:in `save!&#39; 41: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/persistence.rb:507:in `save!&#39; 40: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/timestamp.rb:126:in `create_or_update&#39; 39: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/callbacks.rb:457:in `create_or_update&#39; 38: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/callbacks.rb:824:in `_run_save_callbacks&#39; 37: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/callbacks.rb:98:in `run_callbacks&#39; 36: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/callbacks.rb:457:in `block in create_or_update&#39; 35: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/persistence.rb:900:in `create_or_update&#39; 34: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/timestamp.rb:108:in `_create_record&#39; 33: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/callbacks.rb:461:in `_create_record&#39; 32: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/callbacks.rb:824:in `_run_create_callbacks&#39; 31: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/callbacks.rb:98:in `run_callbacks&#39; 30: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/callbacks.rb:461:in `block in _create_record&#39; 29: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/attribute_methods/dirty.rb:201:in `_create_record&#39; 28: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/locking/optimistic.rb:79:in `_create_record&#39; 27: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/counter_cache.rb:166:in `_create_record&#39; 26: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/persistence.rb:929:in `_create_record&#39; 25: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/persistence.rb:375:in `_insert_record&#39; 24: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/query_cache.rb:22:in `insert&#39; 23: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:171:in `insert&#39; 22: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:136:in `exec_insert&#39; 21: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/mysql/database_statements.rb:55:in `exec_query&#39; 20: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:215:in `execute_and_free&#39; 19: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/mysql/database_statements.rb:50:in `execute&#39; 18: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:204:in `execute&#39; 17: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract_adapter.rb:688:in `log&#39; 16: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/notifications/instrumenter.rb:24:in `instrument&#39; 15: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract_adapter.rb:696:in `block in log&#39; 14: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `synchronize&#39; 13: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `handle_interrupt&#39; 12: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `block in synchronize&#39; 11: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `handle_interrupt&#39; 10: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:26:in `block (2 levels) in synchronize&#39; 9: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract_adapter.rb:697:in `block (2 levels) in log&#39; 8: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:205:in `block in execute&#39; 7: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/dependencies/interlock.rb:47:in `permit_concurrent_loads&#39; 6: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/share_lock.rb:187:in `yield_shares&#39; 5: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/dependencies/interlock.rb:48:in `block in permit_concurrent_loads&#39; 4: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract_mysql_adapter.rb:206:in `block (2 levels) in execute&#39; 3: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/mysql2-0.5.3/lib/mysql2/client.rb:130:in `query&#39; 2: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/mysql2-0.5.3/lib/mysql2/client.rb:130:in `handle_interrupt&#39; 1: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/mysql2-0.5.3/lib/mysql2/client.rb:131:in `block in query&#39; /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/mysql2-0.5.3/lib/mysql2/client.rb:131:in `_query&#39;: Mysql2::Error: Duplicate entry &#39;foo&#39; for key &#39;users.index_users_on_name&#39; (ActiveRecord::RecordNotUnique) 18: from foo.rb:34:in `&lt;main&gt;&#39; 17: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/transactions.rb:209:in `transaction&#39; 16: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:320:in `transaction&#39; 15: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/transaction.rb:308:in `within_new_transaction&#39; 14: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `synchronize&#39; 13: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:21:in `handle_interrupt&#39; 12: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `block in synchronize&#39; 11: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:25:in `handle_interrupt&#39; 10: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activesupport-6.1.0/lib/active_support/concurrency/load_interlock_aware_monitor.rb:26:in `block (2 levels) in synchronize&#39; 9: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/transaction.rb:310:in `block in within_new_transaction&#39; 8: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/connection_adapters/abstract/database_statements.rb:320:in `block in transaction&#39; 7: from foo.rb:35:in `block in &lt;main&gt;&#39; 6: from foo.rb:41:in `rescue in block in &lt;main&gt;&#39; 5: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/querying.rb:22:in `create_or_find_by!&#39; 4: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:217:in `create_or_find_by!&#39; 3: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation.rb:220:in `rescue in create_or_find_by!&#39; 2: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation/finder_methods.rb:87:in `find_by!&#39; 1: from /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation/finder_methods.rb:104:in `take!&#39; /Users/kamipo/.rbenv/versions/2.7.1/lib/ruby/gems/2.7.0/gems/activerecord-6.1.0/lib/active_record/relation/finder_methods.rb:354:in `raise_record_not_found_exception!&#39;: Couldn&#39;t find User with [WHERE `users`.`name` = ?] (ActiveRecord::RecordNotFound)</pre> <p><a href="https://gist.github.com/kamipo/480f9e6fb61bef0f36b1edbccfd30f66">https://gist.github.com/kamipo/480f9e6fb61bef0f36b1edbccfd30f66</a></p> kamipo SELECT ... FOR UPDATE同士でデッドロックさせる hatenablog://entry/26006613665655226 2020-12-15T21:33:59+09:00 2020-12-15T21:33:59+09:00 最近SELECT ... FOR UPDATEでデッドロックする話を何度かしたので。 前職のときにUPDATE同士がデッドロックしてたときに、SELECT ... FOR UPDATEで排他ロックを取ってからUPDATEしてデッドロックを防ぎますってPRをレビューしてたときのことで、複数レコードの排他ロックは一瞬ですべてのレコードのロックを取れるわけではなく、ロックを取る順番が揃っていないと簡単にデッドロックしますよという話です。 https://gist.github.com/kamipo/0bb4e37d58ba18a8cefb8aa02f778231 # frozen_string_li… <p>最近SELECT ... FOR UPDATEで<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%C3%A5%C9%A5%ED%A5%C3%A5%AF">デッドロック</a>する話を何度かしたので。</p> <p>前職のときにUPDATE同士が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%C3%A5%C9%A5%ED%A5%C3%A5%AF">デッドロック</a>してたときに、SELECT ... FOR UPDATEで排他ロックを取ってからUPDATEして<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%C3%A5%C9%A5%ED%A5%C3%A5%AF">デッドロック</a>を防ぎますってPRをレビューしてたときのことで、複数レコードの排他ロックは一瞬ですべてのレコードのロックを取れるわけではなく、ロックを取る順番が揃っていないと簡単に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%C3%A5%C9%A5%ED%A5%C3%A5%AF">デッドロック</a>しますよという話です。</p> <p><a href="https://gist.github.com/kamipo/0bb4e37d58ba18a8cefb8aa02f778231">https://gist.github.com/kamipo/0bb4e37d58ba18a8cefb8aa02f778231</a></p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synComment"># frozen_string_literal: true</span> <span class="synPreProc">require</span> <span class="synSpecial">&quot;</span><span class="synConstant">mysql2</span><span class="synSpecial">&quot;</span> <span class="synPreProc">def</span> <span class="synIdentifier">client</span> <span class="synType">Mysql2</span>::<span class="synType">Client</span>.new( <span class="synConstant">host</span>: <span class="synSpecial">&quot;</span><span class="synConstant">localhost</span><span class="synSpecial">&quot;</span>, <span class="synConstant">username</span>: <span class="synSpecial">&quot;</span><span class="synConstant">root</span><span class="synSpecial">&quot;</span>, <span class="synConstant">database</span>: <span class="synSpecial">&quot;</span><span class="synConstant">test</span><span class="synSpecial">&quot;</span>, ) <span class="synPreProc">end</span> c1 = client c2 = client c1.query(<span class="synSpecial">&quot;</span><span class="synConstant">DROP TABLE IF EXISTS `user_tables`</span><span class="synSpecial">&quot;</span>) c1.query &lt;&lt;-<span class="synSpecial">SQL</span> <span class="synConstant">CREATE TABLE `user_tables` (</span> <span class="synConstant"> `id` int(11) NOT NULL AUTO_INCREMENT,</span> <span class="synConstant"> `name` varchar(255) NOT NULL,</span> <span class="synConstant"> PRIMARY KEY (`id`),</span> <span class="synConstant"> UNIQUE KEY `index_user_tables_on_name` (`name`)</span> <span class="synConstant">)</span> <span class="synSpecial">SQL</span> <span class="synConstant">1000</span>.downto(<span class="synConstant">1</span>) <span class="synStatement">do</span> |<span class="synIdentifier">i</span>| c1.query(<span class="synSpecial">&quot;</span><span class="synConstant">INSERT INTO `user_tables` (`name`) VALUES ('p</span><span class="synSpecial">#{</span>i<span class="synSpecial">}</span><span class="synConstant">')</span><span class="synSpecial">&quot;</span>) <span class="synStatement">end</span> t = <span class="synType">Thread</span>.new <span class="synStatement">do</span> <span class="synConstant">100</span>.times <span class="synStatement">do</span> |<span class="synIdentifier">j</span>| c2.query(<span class="synSpecial">&quot;</span><span class="synConstant">BEGIN</span><span class="synSpecial">&quot;</span>) <span class="synComment">#puts &quot;c2 locking:#{j}&quot;</span> c2.query(<span class="synSpecial">&quot;</span><span class="synConstant">SELECT 1 FROM `user_tables` FORCE INDEX(index_user_tables_on_name) WHERE `name` IN (</span><span class="synSpecial">#{</span><span class="synConstant">1000</span>.downto(<span class="synConstant">1</span>).map{|<span class="synIdentifier">i</span>|<span class="synSpecial">&quot;</span><span class="synConstant">'p</span><span class="synSpecial">#{</span>i<span class="synSpecial">}</span><span class="synConstant">'</span><span class="synSpecial">&quot;</span>}.join(<span class="synSpecial">&quot;</span><span class="synConstant">,</span><span class="synSpecial">&quot;</span>)<span class="synSpecial">}</span><span class="synConstant">) FOR UPDATE</span><span class="synSpecial">&quot;</span>) <span class="synComment">#puts &quot;c2 locked:#{j}&quot;</span> c2.query(<span class="synSpecial">&quot;</span><span class="synConstant">COMMIT</span><span class="synSpecial">&quot;</span>) <span class="synStatement">end</span> <span class="synStatement">end</span> <span class="synConstant">100</span>.times <span class="synStatement">do</span> |<span class="synIdentifier">j</span>| c1.query(<span class="synSpecial">&quot;</span><span class="synConstant">BEGIN</span><span class="synSpecial">&quot;</span>) <span class="synComment">#puts &quot;c1 locking:#{j}&quot;</span> c1.query(<span class="synSpecial">&quot;</span><span class="synConstant">SELECT 1 FROM `user_tables` FORCE INDEX(PRIMARY) WHERE `id` IN (</span><span class="synSpecial">#{</span><span class="synConstant">1</span>.upto(<span class="synConstant">1000</span>).to_a.join(<span class="synSpecial">&quot;</span><span class="synConstant">,</span><span class="synSpecial">&quot;</span>)<span class="synSpecial">}</span><span class="synConstant">) FOR UPDATE</span><span class="synSpecial">&quot;</span>) <span class="synComment">#puts &quot;c1 locked:#{j}&quot;</span> c1.query(<span class="synSpecial">&quot;</span><span class="synConstant">COMMIT</span><span class="synSpecial">&quot;</span>) <span class="synStatement">end</span> t.join </pre> <pre class="code" data-lang="" data-unlink>% be ruby lock.rb /Users/kamipo/.rbenv/versions/2.7.0-dev/lib/ruby/gems/2.7.0/gems/bundler-1.17.3/lib/bundler/rubygems_integration.rb:200: warning: constant Gem::ConfigMap is deprecated /Users/kamipo/.rbenv/versions/2.7.0-dev/lib/ruby/gems/2.7.0/gems/bundler-1.17.3/lib/bundler/rubygems_integration.rb:200: warning: constant Gem::ConfigMap is deprecated #&lt;Thread:0x00007fa6099b38c0@lock.rb:30 run&gt; terminated with exception (report_on_exception is true): Traceback (most recent call last): 6: from lock.rb:31:in `block in &lt;main&gt;&#39; 5: from lock.rb:31:in `times&#39; 4: from lock.rb:34:in `block (2 levels) in &lt;main&gt;&#39; 3: from /Users/kamipo/src/github.com/brianmario/mysql2/lib/mysql2/client.rb:130:in `query&#39; 2: from /Users/kamipo/src/github.com/brianmario/mysql2/lib/mysql2/client.rb:130:in `handle_interrupt&#39; 1: from /Users/kamipo/src/github.com/brianmario/mysql2/lib/mysql2/client.rb:138:in `block in query&#39; /Users/kamipo/src/github.com/brianmario/mysql2/lib/mysql2/client.rb:138:in `_query&#39;: Deadlock found when trying to get lock; try restarting transaction (Mysql2::Error) Traceback (most recent call last): 6: from lock.rb:31:in `block in &lt;main&gt;&#39; 5: from lock.rb:31:in `times&#39; 4: from lock.rb:34:in `block (2 levels) in &lt;main&gt;&#39; 3: from /Users/kamipo/src/github.com/brianmario/mysql2/lib/mysql2/client.rb:130:in `query&#39; 2: from /Users/kamipo/src/github.com/brianmario/mysql2/lib/mysql2/client.rb:130:in `handle_interrupt&#39; 1: from /Users/kamipo/src/github.com/brianmario/mysql2/lib/mysql2/client.rb:138:in `block in query&#39; /Users/kamipo/src/github.com/brianmario/mysql2/lib/mysql2/client.rb:138:in `_query&#39;: Deadlock found when trying to get lock; try restarting transaction (Mysql2::Error)</pre> <p>これはどういう原理で<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%C3%A5%C9%A5%ED%A5%C3%A5%AF">デッドロック</a>させているかというと、プライマリキーと<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BB%A5%AB%A5%F3%A5%C0%A5%EA">セカンダリ</a>キーで意図的に並び順が異なるようなデータを生成して、プライマリキーを使う実行計画のSELECT ... FOR UPDATEと<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BB%A5%AB%A5%F3%A5%C0%A5%EA">セカンダリ</a>キーを使う実行計画のSELECT ... FOR UPDATEが真逆の順序でレコードのロックを取るように仕向けて<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%C3%A5%C9%A5%ED%A5%C3%A5%AF">デッドロック</a>を引き起こさせています。</p> <p>このように、複数レコードのロックは一瞬で同時に起きるわけではなく、順番に起きて途中の状態(ロックが取り終わったレコードとこれからロックを取るつもりのレコードがある状態)が存在するので、ロックを取る順番が一意になるようにクエリや実行計画を揃えるというのがこの手の問題に対する一般的な対処法になります。</p> <p>ロックを取る順番って見えづらいので問題に気づきづらいですよね、かくいう僕も眼の良さを活かして気合いで対処してるので、なんかいい方法あったら教えてください。</p> kamipo ツイッターで見つけて直したActiveRecordの問題さらに3つ hatenablog://entry/26006613660375665 2020-12-04T16:52:12+09:00 2020-12-04T16:52:12+09:00 ツイッターで見つけて直したActiveRecordの問題3つ - かみぽわーるの続き。 where(id: ..1) ("id" <= 1)をnotしたら"id" > 1になってほしい 今のmasterで試してみたのですが、SELECT "users".* FROM "users" WHERE NOT ("users"."id" <= 1) になるようです。 https://t.co/pQh4h9g0MP— 神速 (@sinsoku_listy) August 3, 2019 github.com association先のカラムをpluckしたときもちゃんとtype castされてほしい 対… <p><a href="https://blog.kamipo.net/entry/2020/12/03/162302">&#x30C4;&#x30A4;&#x30C3;&#x30BF;&#x30FC;&#x3067;&#x898B;&#x3064;&#x3051;&#x3066;&#x76F4;&#x3057;&#x305F;ActiveRecord&#x306E;&#x554F;&#x984C;3&#x3064; - &#x304B;&#x307F;&#x307D;&#x308F;&#x30FC;&#x308B;</a>の続き。</p> <ul> <li><code>where(id: ..1)</code> (<code>"id" &lt;= 1</code>)を<code>not</code>したら<code>"id" &gt; 1</code>になってほしい</li> </ul> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">今のmasterで試してみたのですが、SELECT &quot;users&quot;.* FROM &quot;users&quot; WHERE NOT (&quot;users&quot;.&quot;id&quot; &lt;= 1) になるようです。 <a href="https://t.co/pQh4h9g0MP">https://t.co/pQh4h9g0MP</a></p>&mdash; 神速 (@sinsoku_listy) <a href="https://twitter.com/sinsoku_listy/status/1157677315075129344?ref_src=twsrc%5Etfw">August 3, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F39624" title="Make Arel nodes more invertable by kamipo · Pull Request #39624 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/39624">github.com</a></cite></p> <ul> <li>association先のカラムをpluckしたときもちゃんとtype castされてほしい</li> </ul> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">対応ありがとうございます!<br>制限があるとはいえ、定義ではなく呼び出し側で解決を図れるのは嬉しいです。<br><br>ちなみに association の情報を与えてあげるようなアプローチは既に検討済みですか?例えばこういう…<br>User.joins(:association).pluck(association: :column_name)</p>&mdash; _h_s_ (@_h_s_) <a href="https://twitter.com/_h_s_/status/1260862621634359296?ref_src=twsrc%5Etfw">May 14, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F39292" title="Type cast `pluck` values for table name unqalified column in joins tables by kamipo · Pull Request #39292 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/39292">github.com</a></cite></p> <ul> <li>rewhereでちゃんとテーブルを考慮してwhere句を上書きしてほしい</li> </ul> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">rewhere(except) がテーブルを考慮するようになればだいぶ楽になるなあ</p>&mdash; バンビちゃん@実際存在しません (@pink_bangbi) <a href="https://twitter.com/pink_bangbi/status/1200417635667775488?ref_src=twsrc%5Etfw">November 29, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr"><a class="keyword" href="http://d.hatena.ne.jp/keyword/ActiveRecord">ActiveRecord</a> むずかしいにゃんねえ<br>【<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby%20on%20Rails">Ruby on Rails</a> Advent Calendar 2019】<a class="keyword" href="http://d.hatena.ne.jp/keyword/ActiveRecord">ActiveRecord</a> で後から where クエリを上書きする【2日目】 - Secret Garden(Instrumental) <a href="https://t.co/ojNO3TZrBA">https://t.co/ojNO3TZrBA</a></p>&mdash; バンビちゃん@実際存在しません (@pink_bangbi) <a href="https://twitter.com/pink_bangbi/status/1201157225500200960?ref_src=twsrc%5Etfw">December 1, 2019</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F39141" title="Fix `rewhere` to truly overwrite collided where clause by new where clause by kamipo · Pull Request #39141 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/39141">github.com</a></cite></p> kamipo ツイッターで見つけて直したActiveRecordの問題3つ hatenablog://entry/26006613659933605 2020-12-03T16:23:02+09:00 2020-12-04T16:33:55+09:00 Rails Advent Calendar 2020の3日目です。 時間がないのでとりいそぎ3つだけ。 enum state: {active: 0, inactive: 1}とかした時に、typecast前の0とか1を取りたい ActiveRecord::Enumってstate: {active: 0, inactive: 1}とかした時に、typecast前の0とか1を取る場合、read_attribute_before_type_castを使うしかないのか?— アルフォートおじさん (@joker1007) October 21, 2020 github.com belongs_to :… <p><a href="https://qiita.com/advent-calendar/2020/rails">Rails Advent Calendar 2020</a>の3日目です。</p> <p>時間がないのでとりいそぎ3つだけ。</p> <ul> <li><code>enum state: {active: 0, inactive: 1}</code>とかした時に、typecast前の0とか1を取りたい</li> </ul> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr"><a class="keyword" href="http://d.hatena.ne.jp/keyword/ActiveRecord">ActiveRecord</a>::<a class="keyword" href="http://d.hatena.ne.jp/keyword/Enum">Enum</a>ってstate: {active: 0, inactive: 1}とかした時に、typecast前の0とか1を取る場合、read_attribute_before_type_castを使うしかないのか?</p>&mdash; <a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%EB%A5%D5%A5%A9%A1%BC%A5%C8">アルフォート</a>おじさん (@joker1007) <a href="https://twitter.com/joker1007/status/1318882470029262850?ref_src=twsrc%5Etfw">October 21, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F40456" title="Add `attribute_for_database` attribute method by kamipo · Pull Request #40456 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/40456">github.com</a></cite></p> <ul> <li><code>belongs_to :author, class_name: 'User'</code>したときに<code>left_joins(:author).where("author.id": nil)</code>とか書きたい</li> </ul> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">これすると、joins(:author).where(users: { id: <a class="keyword" href="http://d.hatena.ne.jp/keyword/nil">nil</a> }) みたいに書く必要もあったり、各所でオプションが必要になったりで、個人的にはあまり好きじゃない。</p>&mdash; 神速 (@sinsoku_listy) <a href="https://twitter.com/sinsoku_listy/status/1268558861205565443?ref_src=twsrc%5Etfw">June 4, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F40106" title="Allow `where` references association names as joined table alias names by kamipo · Pull Request #40106 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/40106">github.com</a></cite></p> <ul> <li><code>attribute</code>でDBの型情報を保ったままdefault値だけ定義したい</li> </ul> <p><blockquote data-conversation="none" class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr"><a class="keyword" href="http://d.hatena.ne.jp/keyword/ActiveRecord">ActiveRecord</a>でDBから動的にprecisionを取得しつつ<br>attribute(..., default: &#39;&#39;) でデフォルト値を定義する方法、無いな🙄<br><br>誰もそんなことをする人おらんもんな。<br>どうしたもんかねぇ。</p>&mdash; いっくん (@alpaca_tc) <a href="https://twitter.com/alpaca_tc/status/1280449451115474945?ref_src=twsrc%5Etfw">July 7, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> <iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F39830" title="Allow attribute&#39;s default to be configured but keeping its own type by kamipo · Pull Request #39830 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/39830">github.com</a></cite></p> kamipo ISUCON10予選ふりかえり hatenablog://entry/26006613627368306 2020-09-13T18:28:35+09:00 2020-09-13T18:28:35+09:00 ISUCON10予選おつかれさまでした。ISUUMOいい問題でしたね。過去出題側を担当したこともある身でも、参加者の完全攻略に対する怖れもあって仕様が肥大化するなか今回これだけコンパクトな仕様のアプリケーションでこれだけ楽しめる出題をしたのマジですごいと思いました。 今回の問題はMySQLかつ検索ヘヴィな問題で僕のバックグラウンドに向いてる問題にも関わらず、ずっと手を動かしていたわりに効果の高い施策に取り組めず、あらためてISUCONの難しさを痛感したしこれぞISUCONなのだなあと思います。 僕の文章読解が遅く仕様理解にとても時間を要するという性質から、これまでのISUCONでは常にアプリケ… <p>ISUCON10予選おつかれさまでした。ISUUMOいい問題でしたね。過去出題側を担当したこともある身でも、参加者の完全攻略に対する怖れもあって仕様が肥大化するなか今回これだけコンパクトな仕様のアプリケーションでこれだけ楽しめる出題をしたのマジですごいと思いました。</p> <p>今回の問題は<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>かつ検索ヘヴィな問題で僕のバックグラウンドに向いてる問題にも関わらず、ずっと手を動かしていたわりに効果の高い施策に取り組めず、あらためてISUCONの難しさを痛感したしこれぞISUCONなのだなあと思います。</p> <p>僕の文章読解が遅く仕様理解にとても時間を要するという性質から、これまでのISUCONでは常にアプリケーションの仕様や性質を理解できる前に時間的制約からあらゆる決断を迫られるという状況にあり、この状況で仕様や性質を理解できていたとしたらできた正しい決断をしていくのは本当に難しいと思っていて、今回ずっと手を動かしていたわりに結果が振るわなかったのもひとえにその難しさだなあと個人的には思っています。</p> <p>時間を節約するために個人的にとても時間を要する仕様書を読むのを疎かにして失敗したこともあるし、DB<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DC%A5%C8%A5%EB%A5%CD%A5%C3%A5%AF">ボトルネック</a>が支配的ではない問題でスロークエリの対処に時間を投下してアプリケーション仕様を最後までちゃんと理解しないまま失敗したこともある。僕の技術的なバックグラウンドである<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DC%A5%C8%A5%EB%A5%CD%A5%C3%A5%AF">ボトルネック</a>の支配的な要素ではない近年のISUCONで、チームで唯一普段<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>を書いている僕が、自分の有限の時間を何に投下するかというのをその場その場で決断していくのはとてもハードだったなと感じた。だけれど、そのことの精度を上げていくことがISUCONでの結果に繋がっていくのではないかというのが今回ISUCON10予選を終えて感じた個人的な総括になります。</p> <p>今回手元で動かせるアプリケーションでもあったので、今日起きてから予定の合間合間に予選で決断できなかったDB側の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%B6%C1%DB%C0%EF">感想戦</a>をしてみたけど、いやぁこれがその場でできないのがマジでISUCONだなあとあらためて思った。未来のいつかの自分のために今日のこの気持ちをここにしたためておきます。</p> <h4>DB側<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B4%B6%C1%DB%C0%EF">感想戦</a></h4> <p><a href="https://github.com/soudai/isucon10-qualify/pull/12">&#x611F;&#x60F3;&#x6226;: index comment by kamipo &middot; Pull Request #12 &middot; soudai/isucon10-qualify &middot; GitHub</a></p> <p><a href="https://github.com/soudai/isucon10-qualify/pull/13">8e98179ad6 &#x4EE5;&#x964D;&#x306E;&#x30B9;&#x30AD;&#x30FC;&#x30DE;&#x5909;&#x66F4;(index&#x524A;&#x9664;&#x3068;&#x304B;)&#x52B9;&#x3044;&#x3066;&#x306A;&#x304B;&#x3063;&#x305F; by kamipo &middot; Pull Request #13 &middot; soudai/isucon10-qualify &middot; GitHub</a></p> <p><a href="https://github.com/soudai/isucon10-qualify/pull/14">&#x611F;&#x60F3;&#x6226;: Add descending index `popularity DESC` by kamipo &middot; Pull Request #14 &middot; soudai/isucon10-qualify &middot; GitHub</a></p> <p><a href="https://github.com/soudai/isucon10-qualify/pull/15">&#x4E0D;&#x8981;&#x306A;index&#x524A;&#x9664; by kamipo &middot; Pull Request #15 &middot; soudai/isucon10-qualify &middot; GitHub</a></p> <p><a href="https://github.com/soudai/isucon10-qualify/pull/16">Optimize get &#39;/api/recommended_estate/:id&#39; by kamipo &middot; Pull Request #16 &middot; soudai/isucon10-qualify &middot; GitHub</a></p> <p><a href="https://github.com/soudai/isucon10-qualify/pull/17">Optimize post &#39;/api/chair/buy/:id&#39; by kamipo &middot; Pull Request #17 &middot; soudai/isucon10-qualify &middot; GitHub</a></p> <p><a href="https://github.com/soudai/isucon10-qualify/pull/18">Avoid N+1 in post &#39;/api/estate/nazotte&#39; by kamipo &middot; Pull Request #18 &middot; soudai/isucon10-qualify &middot; GitHub</a></p> kamipo Treasure Dataを退職します hatenablog://entry/26006613620041761 2020-08-26T14:54:10+09:00 2020-08-26T17:58:35+09:00 急なお知らせですが、8月31日をもってTreasure Dataを退職することになりました。 今後の活動についてはいまのところなにも決まっていないので、自分になにができるのか、どんなニーズがあるのか、いろいろ相談に乗ってもらえるとうれしいです。 きっかけはというと、長年Railsコントリビューター/メンテナーとして並々ならぬ思いで活動してきたんですが。 どのぐらいがんばっていたかというと、たとえば2020年8月時点のコミット数ベースの今年のアクティビティでいうと、上位10人のアクティビティを母数にするとその半数が僕になります。 rails/rails contributors 2020-01-… <p>急なお知らせですが、8月31日をもってTreasure Dataを退職することになりました。</p> <p>今後の活動についてはいまのところなにも決まっていないので、自分になにができるのか、どんなニーズがあるのか、いろいろ相談に乗ってもらえるとうれしいです。</p> <p>きっかけはというと、長年<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>コントリビューター/メンテナーとして並々ならぬ思いで活動してきたんですが。</p> <p>どのぐらいがんばっていたかというと、たとえば2020年8月時点のコミット数ベースの今年のアクティビティでいうと、上位10人のアクティビティを母数にするとその半数が僕になります。</p> <p><a href="https://github.com/rails/rails/graphs/contributors?from=2020-01-01&amp;to=2020-08-26&amp;type=c">rails/rails contributors 2020-01-01 - 2020-08-26</a></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a> 5.0以降のも置いておきます。</p> <p><a href="https://github.com/rails/rails/graphs/contributors?from=2019-01-01&amp;to=2019-12-31&amp;type=c">rails/rails contributors 2019-01-01 - 2019-12-31</a><br /> <a href="https://github.com/rails/rails/graphs/contributors?from=2018-01-01&amp;to=2018-12-31&amp;type=c">rails/rails contributors 2018-01-01 - 2018-12-31</a><br /> <a href="https://github.com/rails/rails/graphs/contributors?from=2017-01-01&amp;to=2017-12-31&amp;type=c">rails/rails contributors 2017-01-01 - 2017-12-31</a><br /> <a href="https://github.com/rails/rails/graphs/contributors?from=2016-01-01&amp;to=2016-12-31&amp;type=c">rails/rails contributors 2016-01-01 - 2016-12-31</a></p> <p>さすがにこんなにがんばっとるねんからこれ仕事ってことにならん?という思いをずっと持っていて、オンライン飲みで友人にそのことを相談したらメンテナーのポジションで選考してもらえて、そこではマッチングには至らなかったんですが、それでなんか吹っ切れたというか。</p> <p>自分になにができるのか、どんなニーズがあるのか、どんな選択肢があるのか、それを探すのがいま一番やりたいことだなと。</p> <p>こんな時期なのでなかなか難しいとは思うんですが、いろいろ相談に乗ってもらえたり、あとずっと引きこもってこんな活動を続けていたため知り合いが少ないので、誰かしら繋いでくれたりするとうれしいです。人生の新たな楽しみを見つけたいです。</p> <p>とりあえず飲みにいきたい。遊びに誘ってくれても全然オッケーです。</p> kamipo Ruby 2.7.0でキーワード引数として渡された引数なのかどうかフラグを確かめる方法 hatenablog://entry/26006613526598503 2020-02-26T19:39:04+09:00 2020-02-26T19:42:05+09:00 class Hash class << self def ruby2_keywords_hash?(hash) !new(*[hash]).default.equal?(hash) end def ruby2_keywords_hash(hash) _ruby2_keywords_hash(**hash) end private def _ruby2_keywords_hash(*args) args.last end ruby2_keywords(:_ruby2_keywords_hash) if respond_to?(:ruby2_keywords, true) end end RUBY… <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">class</span> <span class="synType">Hash</span> <span class="synPreProc">class</span> &lt;&lt; <span class="synConstant">self</span> <span class="synPreProc">def</span> <span class="synIdentifier">ruby2_keywords_hash?</span>(hash) !new(*[hash]).default.equal?(hash) <span class="synPreProc">end</span> <span class="synPreProc">def</span> <span class="synIdentifier">ruby2_keywords_hash</span>(hash) _ruby2_keywords_hash(**hash) <span class="synPreProc">end</span> <span class="synStatement">private</span> <span class="synPreProc">def</span> <span class="synIdentifier">_ruby2_keywords_hash</span>(*args) args.last <span class="synPreProc">end</span> ruby2_keywords(<span class="synConstant">:_ruby2_keywords_hash</span>) <span class="synStatement">if</span> respond_to?(<span class="synConstant">:ruby2_keywords</span>, <span class="synConstant">true</span>) <span class="synPreProc">end</span> <span class="synPreProc">end</span> <span class="synIdentifier">RUBY_VERSION</span> <span class="synComment"># =&gt; &quot;2.7.0&quot;</span> <span class="synPreProc">def</span> <span class="synIdentifier">passed_kw?</span>(*args) <span class="synType">Hash</span>.ruby2_keywords_hash?(args.last) <span class="synPreProc">end</span> ruby2_keywords(<span class="synConstant">:passed_kw?</span>) <span class="synStatement">if</span> respond_to?(<span class="synConstant">:ruby2_keywords</span>, <span class="synConstant">true</span>) passed_kw?({ <span class="synConstant">a</span>: <span class="synConstant">1</span> }) <span class="synComment"># =&gt; false</span> passed_kw?(<span class="synConstant">a</span>: <span class="synConstant">1</span>) <span class="synComment"># =&gt; true</span> hash = { <span class="synConstant">a</span>: <span class="synConstant">1</span> } passed_kw?(hash) <span class="synComment"># =&gt; false</span> passed_kw?(**hash) <span class="synComment"># =&gt; true</span> kw = <span class="synType">Hash</span>.ruby2_keywords_hash(hash) passed_kw?(kw) <span class="synComment"># =&gt; true</span> passed_kw?(**kw) <span class="synComment"># =&gt; true</span> </pre> <p>説明書こうとしたけどだいぶめんどくさかったのでコードで感じ取ってほしいんですが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> 2.7.0以降<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> 3に向けてキーワード引数渡しの非互換をハンドリングするために<code>ruby2_keywords</code>を使ってキーワード引数渡しされたhashオブジェクトにフラグを付けることができる、というか<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> 2.6以前と<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> 2.7以降の両方サポートしたいライブラリメンテナはこのフラグを付けて回らなければ<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> 3ではArgumentErrorでお前はもう死んでいる状態になっています。</p> <p>フラグが付けれるのはいいとして<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> 2.7.0ではこのフラグが付いてるhashオブジェクトなのかそうじゃないhashオブジェクトなのか確かめる方法が公式には提供されてなくて、どこで呼び出されたときにフラグを付ける必要があって、ちゃんとフラグが付いたままお届け先のメソッドまでたどり着いてるのかの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>が死ぬほどめんどくさかった。</p> <p>これは<a href="https://github.com/rails/rails/pull/38105#discussion_r361863767">mameさんのハック</a>を見て知った方法で、ようはフラグが付いてるhashオブジェクトかそうじゃないオブジェクトかで違う振る舞いをする処理を通らせてその結果を観測することでどっちだったかを確かめるという方法です。</p> <p>このフラグが付いてるhashオブジェクトかそうじゃないオブジェクトかで違う振る舞いをする処理が<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>の世界からはほとんど存在しないので、普段<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>でコードを書いてる常人が自力では気づけんやろって方法で、一部のメソッド(<code>initialize</code>とか<code>method_missing</code>とか)を<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>のコードから直接呼び出すんじゃなくてCのコードから間接的に呼び出されるときにフラグが付いてるhashオブジェクトを<code>dup</code>するので、<code>dup</code>されずにそのままのオブジェクト(<code>object_id</code>)だったらフラグが付いてなかったってことで<code>dup</code>されて別のオブジェクトが観測されたらフラグが付いてたオブジェクトだったという技を使っています。</p> <p><blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">いろいろ試行錯誤してみて<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> 2.7.0の時点ではnewを通さないとargs lastにkwargs flagが付いてるかどうか<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a>の世界からは確かめようがないということが分かったけどこれはマジで答えを先に知ってないと気づきようがない無理ゲーすぎた</p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/1218721222474620928?ref_src=twsrc%5Etfw">January 19, 2020</a></blockquote><script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></p> <p>原理が分かったので既存のメソッドでこの用途に丁度いいメソッドを探した結果、<code>Hash.new</code>にhashのデフォルト値としてフラグ付きかもしれないhashオブジェクトをsplat渡しして<code>Hash#default</code>で取り出して観測するという方法が一番シンプルであろうというところに至り、この技を使って<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>でもキーワード引数完全分離への対応を進めています。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F38267" title="Fix keyword arguments warnings in Active Job by kamipo · Pull Request #38267 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/38267">github.com</a></cite></p> <p>このフラグ付きかどうか確かめる方法があるのかないのか常人では思い至らん問題にライブラリメンテナは直面すると思いますが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> 2.7.1にはフラグ付きかどうか確かめる方法が公式にバックポートされる予定なので、これで警告が多い日も安心ですね。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Fruby%2Fruby%2Fpull%2F2818" title="hash.c: Add a feature to manipulate ruby2_keywords flag by mame · Pull Request #2818 · ruby/ruby" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/ruby/ruby/pull/2818">github.com</a></cite></p> kamipo ISUCON9予選ふりかえり hatenablog://entry/26006613425443803 2019-09-09T04:11:27+09:00 2019-09-09T04:18:39+09:00 なにもできなかったし書くことないわーって思ってたけど、チームのふりかえりをGitHubのissueに書いてたらふつうにこれ外に書けばいいなってなったのでここに記す。 アクセスログから得られる情報の解像度が相対的に低くなってきたと感じた。ざっくりどのエンドポイントが遅いとか何回叩かれてるとかの概観を知るのは依然として重要だけど、近年は遅いエンドポイントの処理はめちゃくちゃ複雑になってきていて、たとえば/buyが遅いぐらいの情報の解像度では情報量が足りてなかった。 外部APIへのリクエストが絡むような問題への心の準備とかできてなかった。なんなら準備してきてないし予選では出ないでほしいなとか思ってい… <p>なにもできなかったし書くことないわーって思ってたけど、チームのふりかえりを<a class="keyword" href="http://d.hatena.ne.jp/keyword/GitHub">GitHub</a>のissueに書いてたらふつうにこれ外に書けばいいなってなったのでここに記す。</p> <ul> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B9%A5%ED%A5%B0">アクセスログ</a>から得られる情報の解像度が相対的に低くなってきたと感じた。ざっくりどのエンドポイントが遅いとか何回叩かれてるとかの概観を知るのは依然として重要だけど、近年は遅いエンドポイントの処理はめちゃくちゃ複雑になってきていて、たとえば/buyが遅いぐらいの情報の解像度では情報量が足りてなかった。</li> <li>外部<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>へのリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トが絡むような問題への心の準備とかできてなかった。なんなら準備してきてないし予選では出ないでほしいなとか思っていた。</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%ED%A5%B0%A5%E9%A5%DE">プログラマ</a>としての筋力が足りてなかった。たとえば<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%AF%A5%BB%A5%B9%A5%ED%A5%B0">アクセスログ</a>からの情報では足りないってなったときにときにじゃあやばそうなところ全部にprint文書いて測るわ!ぐらいの気概が必要だった。外部リク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トが遅いとかロック競合で時間がかかっていると遅いけどあまりリソースは食ってないように見えるしそういう面でもprint文書いてでも知りたい情報ログに出したるわぐらいの気概が必要だった。</li> <li>外部<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>へのリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トが遅いかもってなったときに遅いリソースへのリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トを多重化するようなプログラミングを普段してなかったのでひよった。きれいに言語の<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>やライブラリが使えなくてもたとえばThread.newしまくってでもやったるわぐらいの気概が必要だった。</li> <li>もはやデータベースに入ってるデータだけじゃなくて外部<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>のレスポンスも含めてアプリケーションを構成するデータソースだという考えで挑まなければならないと思った。実際外部<a class="keyword" href="http://d.hatena.ne.jp/keyword/API">API</a>のレスポンスの内容がリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>トに対して不変な内容かそうでないか、不変ならキャッシュ可能かという考えに至れていなかった(もしキャッシュ可能だと分かれば不慣れなリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ト多重化プログラミングを避けることができたかもしれない)。</li> <li>自分たちに相性がいい過去問で練習してできることをやればいけそうって過信してしまった。苦手だった問題も過去にはあるのにそういう苦手分野に向き合うことをおざなりにしてしまったところはいなめなかった。</li> </ul> <p>ISUCON9予選、手も足もでなかったけど、いい問題だと思ったし楽しめた。近年データベースが<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DC%A5%C8%A5%EB%A5%CD%A5%C3%A5%AF">ボトルネック</a>じゃない問題で僕の楽しみがあまりないからISUCONから遠ざかってたところあったけどISUCON8予選がデータベースのロック競合を出題に盛り込んできて、そういう問題ならやってみたかったって思ったし、今回の問題は去年の予選問題のようでもありそこからさらに去年の本選問題を足してひねりが加えられたような一筋縄ではいかない感じであった。今回誘ってくれた<a href="http://blog.hatena.ne.jp/Soudai/">id:Soudai</a>さんと一緒に戦ってくれた<a href="http://blog.hatena.ne.jp/sugyan/">id:sugyan</a>にまじ感謝、楽しかったです。</p> kamipo Rails 6.0の複数DBでリードレプリカのテストするのたぶん大変 hatenablog://entry/17680117127207574352 2019-06-26T10:21:00+09:00 2019-06-26T11:24:24+09:00 Rails 6.0の複数DBのレビューしてるときに気づいたことなんですけど、たぶんリードレプリカからデータを読むテストをするのたぶん大変だと思われます。 うちの業務のアプリでActive Recordが更新を検知できない方法でデータが更新されるとテストがコケるという問題が以前にあり、これと同じ構造の問題がマスターのコネクションで更新したときマスターのコネクションのクエリキャッシュはクリアされるけどリードレプリカのコネクションのクエリキャッシュは残ったままというのがあるよね、というのをテストコードで示そうと思ったときのことである。 github.com 通常RailsアプリでDBつかったテストを… <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a> 6.0の複数DBのレビューしてるときに気づいたことなんですけど、たぶんリードレプリカからデータを読むテストをするのたぶん大変だと思われます。</p> <p>うちの業務のアプリでActive Recordが更新を検知できない方法でデータが更新されるとテストがコケるという問題が以前にあり、これと同じ構造の問題がマスターのコネクションで更新したときマスターのコネクションのクエリキャッシュはクリアされるけどリードレプリカのコネクションのクエリキャッシュは残ったままというのがあるよね、というのをテストコードで示そうと思ったときのことである。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F35073%23issuecomment-458519770" title="Part 8: Multi db improvements, Adds basic automatic database switching to Rails by eileencodes · Pull Request #35073 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/35073#issuecomment-458519770">github.com</a></cite></p> <p>通常<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>アプリでDBつかったテストをするとき、テストの中で変更されたデータを毎回初期状態に戻すのにフィクスチャーをロードし直すのは時間がかかって効率がわるいので、テストケースに入る前に<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>を開始しといてテストケース終わったら<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%ED%A1%BC%A5%EB%A5%D0%A5%C3%A5%AF">ロールバック</a>して変更をなかったことにすることで初期状態に戻し、時間のかかるフィクスチャーロードし直しを避けるということが効率のために行われています。</p> <p>この、効率のためにテストケース終わったら<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%ED%A1%BC%A5%EB%A5%D0%A5%C3%A5%AF">ロールバック</a>して変更をなかったことにするためにテスト内の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>はネストした<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C8%A5%E9%A5%F3%A5%B6%A5%AF%A5%B7%A5%E7%A5%F3">トランザクション</a>になっていて実際にコミットは起きないということが複数DBのリードレプリカと相性がとてもわるい。実際にコミットが起きないことにはリードレプリカに更新がレプリケートされてこないのでずっと時が止まった状態なのである。</p> <p>あたかもマスターで更新が起きたっぽいときにリードレプリカにも更新が伝搬しているかのように見せかけるため、Active Recordはテストのときデフォルトですべてのコネクションプールをマスターのコネクションプールにすり替えるということで対処している。コミットが起きないんだったら全部マスターに接続してテストすればいいじゃないってやつです。しかしこれをされるとマスターではなくリードレプリカからデータを読んでることをテストしたいときにめっちゃこまるという話である。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F34773%23discussion_r252124180" title="For fixtures share the connection pool when there are multiple handlers by eileencodes · Pull Request #34773 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/34773#discussion_r252124180">github.com</a></cite></p> <p>コード読んだ限りではフィクスチャー毎回読み直しモードのときは <code>setup_shared_connection_pool</code> は呼ばれないから大丈夫そうに見えるけど、自分が試したときにはフィクスチャー毎回読み直しモードのときでも <code>setup_shared_connection_pool</code> の効果を自力で打ち消さないとまったくリードレプリカに接続できなくてめっちゃ苦労したので、先人の記憶としてここに書き記しておきます。同じ問題にぶち当たったりぶち当たらなかったりなんか分かった人いて教えてくれたらここに追記します。</p> <p>あと、リードレプリカでクエリキャッシュ残る問題はがんばってテスト書いて修正される運びとなったのでこれで更新の多い日も安心ですね。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F35089" title="Invalidate all query caches for current thread by eileencodes · Pull Request #35089 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/35089">github.com</a></cite></p> kamipo Rails 6.0でDeprecatedになるActive Recordの振る舞い3つ hatenablog://entry/17680117127131291299 2019-05-15T15:26:52+09:00 2019-05-15T15:26:52+09:00 Deprecatedにした経緯というか背景が伝わってるのかどうかアレだと思ったので、ここに日本語にて書き記しておく。 Deprecate mismatched collation comparison for uniquness validator by kamipo · Pull Request #35350 · rails/rails · GitHub Active Recordのuniqueness validatorはデフォルトでcase sensitiveな比較をするんですが、これが、文字列のデフォルトのcollationがcase insensitiveなMySQLと相性が悪く、D… <p>Deprecatedにした経緯というか背景が伝わってるのかどうかアレだと思ったので、ここに日本語にて書き記しておく。</p> <ul> <li><a href="https://github.com/rails/rails/pull/35350">Deprecate mismatched collation comparison for uniquness validator by kamipo &middot; Pull Request #35350 &middot; rails/rails &middot; GitHub</a></li> </ul> <p>Active Recordのuniqueness validatorはデフォルトで<a class="keyword" href="http://d.hatena.ne.jp/keyword/case%20sensitive">case sensitive</a>な比較をするんですが、これが、文字列のデフォルトのcollationが<a class="keyword" href="http://d.hatena.ne.jp/keyword/case%20insensitive">case insensitive</a>な<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>と相性が悪く、DB上のUNIQUE制約と一致しない振る舞いだったりINDEXが効率よく使えずDBが死ぬみたいな問題を引き起こしていました。</p> <p>例: <a href="https://www.slideshare.net/ssuserf3788f/rails-83053313/12">&#x672C;&#x5F53;&#x306B;&#x3042;&#x3063;&#x305F;Rails&#x306E;&#x6016;&#x3044;&#x8A71;</a></p> <p>僕も主に仕事のコードレビューで過去に何度もこの問題を指摘してきたわけですが、去年ぐらいにmakimotoさんにこれ<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>側でなんとかならないんですか的なこと言われてそりゃ同僚に<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>コミッターおったら仕事で遭遇した<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>側のあらゆる問題は<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>側でなんとかなってほしいわなって思ったので気合いでなんとかすることにしたという次第です。</p> <p>いないとは思いますが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>などでデフォルトのcollationが<a class="keyword" href="http://d.hatena.ne.jp/keyword/case%20insensitive">case insensitive</a>な文字列をあえてわざと<a class="keyword" href="http://d.hatena.ne.jp/keyword/case%20sensitive">case sensitive</a>な比較をしていた奇特なユーザーの皆様は今後明示的に <code>case_sensitive: true</code> オプションを指定していただくことになります。</p> <ul> <li><a href="https://github.com/rails/rails/pull/36029">Deprecate `where.not` working as NOR and will be changed to NAND in Rails 6.1 by kamipo &middot; Pull Request #36029 &middot; rails/rails &middot; GitHub</a></li> </ul> <p><code>where.not(a: ..., b: ...)</code> の結果が <code>where(a: ..., b: ...)</code> の補集合になるようになります。逆にいうと6.1になるまでは <code>where.not(a: ..., b: ...)</code> の結果は <code>where(a: ..., b: ...)</code> の補集合になるとは限りません。</p> <p>想像してほしいんですが、血液型B型の男性(私です)の集合があったとして、それのNOTを取った集合は血液型B型の男性以外になってほしいと思いませんか?僕は思います。現状、Active Recordの <code>where.not</code> は血液型B型の男性という集合を反転した結果として、男性でもなく血液型B型でもない集合を返します。もしこれが想像していた振る舞いと違う場合、アプリケーションは暗黙のうちにバグってる可能性があるので <code>where.not</code> を使っている皆さんは手元のコードをよく確認してみるのがよいのではないかと思います。</p> <ul> <li><a href="https://github.com/rails/rails/pull/35280">Deprecate using class level querying methods if the receiver scope regarded as leaked by kamipo &middot; Pull Request #35280 &middot; rails/rails &middot; GitHub</a></li> </ul> <p>これは説明がむずかしいというか面倒なんですけど、たとえばTopicのツリー型の<a class="keyword" href="http://d.hatena.ne.jp/keyword/%B7%C7%BC%A8">掲示</a>板みたいなものがあったとして、トッ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%D7%A5%EC%A5%D9">プレベ</a>ルのTopic、かつその中でも子スレッドを持つものをscopeとして定義したいとします。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synPreProc">class</span> <span class="synType">Topic</span> &lt; <span class="synType">ActiveRecord</span>::<span class="synType">Base</span> scope <span class="synConstant">:toplevel</span>, -&gt; { where(<span class="synConstant">parent_id</span>: <span class="synConstant">nil</span>) } scope <span class="synConstant">:children</span>, -&gt; { where.not(<span class="synConstant">parent_id</span>: <span class="synConstant">nil</span>) } scope <span class="synConstant">:has_children</span>, -&gt; { where(<span class="synConstant">id</span>: <span class="synType">Topic</span>.children.select(<span class="synConstant">:parent_id</span>)) } <span class="synPreProc">end</span> <span class="synComment"># Works as expected.</span> <span class="synType">Topic</span>.toplevel.where(<span class="synConstant">id</span>: <span class="synType">Topic</span>.children.select(<span class="synConstant">:parent_id</span>)) <span class="synComment"># Doesn't work due to leaking `toplevel` to `Topic.children`.</span> <span class="synType">Topic</span>.toplevel.has_children </pre> <p>子スレッドを持つという条件を <code>has_children</code> として抽出して利用したいわけですが、これは現状ほぼ意図した通りには動きません。というのもscopeのメソッドチェインは条件が累積したrelationを伝搬させるのにクラスグローバルな状態を汚染する実装方法を現状とっており、scope定義内はその汚染されたクラスグローバルな状態の影響を受けるためです。</p> <p>現在、我々がどのようにしてこの問題を回避しているかというと <code>Topic.unscoped.children.select(:parent_id)</code> のように <code>unscoped</code> をつけるのを忘れないように頑張る、というものですが、6.1からは <code>unscoped</code> をつけるのを忘れないように頑張らなくてもよくなるということになります。</p> <p>逆にいうと、現状scope定義内でモデルクラスから直接scopeやquery methodsを呼び出してるケースではユーザーの期待に反して意図せず別のscopeの状態が注入されている可能性があるので、今一度手元のコードを確認されるのがよいかと思います。</p> <p>以上、よろしくお願いいたします。</p> kamipo Rubyで安全な文字列リテラルかどうかを判別したい hatenablog://entry/10257846132654215264 2018-10-16T09:30:10+09:00 2018-10-16T10:51:40+09:00 Rails 5.2からRails SQL Injection ExamplesにあるようなSQLインジェクションを防ぐ仕組みが導入されて、Post.order(params[:order])みたいなコードは心温まる正規表現によるチェックをパスしないと危険とみなされるようになって、お前が安全やと思うんやったらPost.order(Arel.sql(params[:order]))しろってことになった(rails/rails#27947)。 これはRails 5.0のときにparamsがHashのサブクラスじゃなくなったときに比べればマシだけど、明らか安全やと思ってるリテラルもRailsに危険とみ… <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a> 5.2から<a href="https://rails-sqli.org/">Rails SQL Injection Examples</a>にあるような<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL%A5%A4%A5%F3%A5%B8%A5%A7%A5%AF%A5%B7%A5%E7%A5%F3">SQLインジェクション</a>を防ぐ仕組みが導入されて、<code>Post.order(params[:order])</code>みたいなコードは心温まる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%B5%B5%AC%C9%BD%B8%BD">正規表現</a>によるチェックをパスしないと危険とみなされるようになって、お前が安全やと思うんやったら<code>Post.order(Arel.sql(params[:order]))</code>しろってことになった(<a href="https://github.com/rails/rails/pull/27947">rails/rails#27947</a>)。</p> <p>これは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a> 5.0のときに<code>params</code>がHashのサブクラスじゃなくなったときに比べればマシだけど、明らか安全やと思ってる<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%EB">リテラル</a>も<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>に危険とみなされて既存のアプリケーションによったら非常にわずらわしい。たとえばDiscourseという<a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a>アプリは5.2に上げるときにこれの影響をモロに受けるんやけどっていうお気持ちを表明しています(<a href="https://github.com/rails/rails/issues/32995#issuecomment-394899008">rails/rails#32995 (comment)</a>)。</p> <p>そこでこのわずらわしさを軽減する方法を考えたんですけど、もし<code>#order</code>に渡される値がユーザーのリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ト由来の未知の値でなく<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>上に直に書かれた文字列<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%EB">リテラル</a>だったとしたら、これは安全ってことにしていいと思いませんか?僕はそう思いました。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%BD%A1%BC%A5%B9%A5%B3%A1%BC%A5%C9">ソースコード</a>上に直に書かれた文字列<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%EB">リテラル</a>かどうかを完全に判別できる方法は今のところおそらく存在しないと思うけど、ユーザーのリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ト由来じゃないかどうかをできるときだけできる範囲で区別するというのだと、<code># frozen_string_literal: true</code>のときにユーザーのリク<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A8%A5%B9">エス</a>ト由来の値とちがって文字列<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%EB">リテラル</a>はfrozenになるという性質が利用できそうです。という方法を実装したのが以下。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F33330" title="Allow frozen string literal as a safe raw SQL string by kamipo · Pull Request #33330 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/33330">github.com</a></cite></p> <p>これにはひとつ物言いがついて、<code>"#{user_input}"</code>がfrozenにならんのやったらええけどそこがなー的なこと言われてマジカーってなってたら、interpolated stringを判別する方法を教えてくれる神が降臨した(<a href="https://github.com/rails/rails/pull/33330#issuecomment-414159201">rails/rails#33330 (comment)</a>)。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synComment"># frozen_string_literal: true</span> dynamic = <span class="synIdentifier">DATA</span>.read constant = <span class="synSpecial">&quot;</span><span class="synConstant">bar</span><span class="synSpecial">&quot;</span> interpolated = <span class="synSpecial">&quot;#{</span>dynamic<span class="synSpecial">}</span><span class="synConstant"> DESC</span><span class="synSpecial">&quot;</span> puts <span class="synSpecial">&quot;</span><span class="synConstant">[dynamic] frozen: </span><span class="synSpecial">#{</span>dynamic.frozen?<span class="synSpecial">}</span><span class="synConstant">, interned: </span><span class="synSpecial">#{</span>dynamic.equal?(-dynamic.dup)<span class="synSpecial">}&quot;</span> puts <span class="synSpecial">&quot;</span><span class="synConstant">[constant] frozen: </span><span class="synSpecial">#{</span>constant.frozen?<span class="synSpecial">}</span><span class="synConstant">, interned: </span><span class="synSpecial">#{</span>constant.equal?(-constant.dup)<span class="synSpecial">}&quot;</span> puts <span class="synSpecial">&quot;</span><span class="synConstant">[interpolated] frozen: </span><span class="synSpecial">#{</span>interpolated.frozen?<span class="synSpecial">}</span><span class="synConstant">, interned: </span><span class="synSpecial">#{</span>interpolated.equal?(-interpolated.dup)<span class="synSpecial">}&quot;</span> <span class="synSpecial">__END__</span> <span class="synComment">foo</span> </pre> <pre class="code" data-lang="" data-unlink>[dynamic] frozen: false, interned: false [constant] frozen: true, interned: true [interpolated] frozen: true, interned: false</pre> <p>パッと見なにを判別してるんかよくわからんかったので<a href="https://twitter.com/nalsh">同僚のRubyコミッター</a>に聞いたらプリント<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%C7%A5%D0%A5%C3%A5%B0">デバッグ</a>しながらいろいろ調べてくれた結果すべてを理解することに成功したのでその成果をここでシェアしたいと思います。</p> <p>これはfrozen literalが<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> 2.5で導入されたfstring tableに登録されているものと同一かどうかを判別していて、constant literalだとfstring tableに登録された<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>を返すけどinterpolated literalだとただのfrozenなだけの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>になるという性質の違いを利用しています。<code>String#-@</code>がfstringを返すので<code>constant.equal?(-constant.dup)</code>はfstring tableからlookupしなおした<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A4%A5%F3%A5%B9%A5%BF%A5%F3%A5%B9">インスタンス</a>が自分自身と同一だったら自分はfstringだったということでつまりconstant literalだったということがわかります。ちなみに<code>.dup</code>が必要なのは<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> 2.5では自分がfrozenだったら<code>String#-@</code>はfstringではなく自分をそのまま返してしまう問題があるからで、この問題は<a class="keyword" href="http://d.hatena.ne.jp/keyword/ruby">ruby</a>-headでは解決済みなので<code>.dup</code>は要らなくなります(<a href="https://github.com/ruby/ruby/commit/256411b47fd486b40eb3cfe3760dca06f62c830f">ruby/ruby@256411b</a>)。</p> <p>この方法は判別しようとするinterpolated literalのバリエーションだけfstring tableに登録してしまうので、なんか他にリーズナブルな方法ないかなって調べてくれたんですけど、今のところなさそう、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%E6%A1%BC%A5%B9%A5%B1%A1%BC%A5%B9">ユースケース</a>次第ですかねハハハーって感じでお開きとなりました。</p> <p>以上、<a class="keyword" href="http://d.hatena.ne.jp/keyword/Ruby">Ruby</a> 2.5以上で文字列<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%C6%A5%E9%A5%EB">リテラル</a>を判別する方法でした。</p> kamipo MySQLのクエリの良し悪しはrows_examinedで判断する hatenablog://entry/17391345971628148720 2018-03-22T08:41:26+09:00 2018-03-23T11:00:36+09:00 仕事やらなんやらでMySQLのクエリの良し悪しを判断する必要があるとき、EXPLAINの内容だけだとどのぐらい良くなったり悪くなったのか分からないので SET long_query_time = 0; してrows_examined (そのクエリでrows_sent行の結果を返すために何行に触ったのか)も一緒に提示するようにしている(少なくともMySQL 5.7時点ではrows_examinedはslow_query_logでしか確認できないはずperformance_schemaが有効ならevents_statements_historyやその仲間たちで確認できるとのこと*1 MySQL :… <p>仕事やらなんやらで<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>のクエリの良し悪しを判断する必要があるとき、EXPLAINの内容だけだとどのぐらい良くなったり悪くなったのか分からないので <code>SET long_query_time = 0;</code> してrows_examined (そのクエリでrows_sent行の結果を返すために何行に触ったのか)も一緒に提示するようにしている(<del>少なくとも<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7時点ではrows_examinedはslow_query_logでしか確認できないはず</del>performance_schemaが有効ならevents_statements_<a class="keyword" href="http://d.hatena.ne.jp/keyword/history">history</a>やその仲間たちで確認できるとのこと<a href="#f-a62cbb9c" name="fn-a62cbb9c" title="https://twitter.com/RDBMS/status/976955535076466688">*1</a> <a href="https://dev.mysql.com/doc/refman/5.6/ja/performance-schema-statement-tables.html">MySQL :: MySQL 5.6 &#x30EA;&#x30D5;&#x30A1;&#x30EC;&#x30F3;&#x30B9;&#x30DE;&#x30CB;&#x30E5;&#x30A2;&#x30EB; :: 22.9.6 &#x30D1;&#x30D5;&#x30A9;&#x30FC;&#x30DE;&#x30F3;&#x30B9;&#x30B9;&#x30AD;&#x30FC;&#x30DE;&#x30B9;&#x30C6;&#x30FC;&#x30C8;&#x30E1;&#x30F3;&#x30C8;&#x30A4;&#x30D9;&#x30F3;&#x30C8;&#x30C6;&#x30FC;&#x30D6;&#x30EB;</a>)。</p> <p>例:</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kamipo/20180322/20180322074906.png" alt="f:id:kamipo:20180322074906p:plain" title="f:id:kamipo:20180322074906p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>上の例のBeforeは、もともとDBAが書いた温かみのある<a class="keyword" href="http://d.hatena.ne.jp/keyword/SQL">SQL</a>でORDER BY LIMIT最適化を効かせてLIMIT 20されたクエリをORDER BY狙いのインデックスを狙って20行の結果を返すために20行だけに触れている、つまり最も良い実行計画のクエリが、Afterでは深遠な理由により別のテーブルの条件で絞り込んだりしたりしなかったりするためのJOINで最適化の狙いが効かなくなって同じ20行の結果を返すために259454行に触れてしまうようになってしまったの図です。</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kamipo/20180322/20180322074925.png" alt="f:id:kamipo:20180322074925p:plain" title="f:id:kamipo:20180322074925p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>仕様を保ったまま最適化の狙いが効くようにクエリの書き変えを行うと、20行の結果を返すために駆動表と内部表それぞれ20行ずつだけ触るのが最も良い実行計画になるので、その修正案をrows_examinedと共に提示しているのが上の図です。</p> <p>ゴルフで例えると、EXPLAINは大まかな方向性や飛距離を合わせるドライバーのような感じで、グリーンによせたあとカップを狙うパター的なのがrows_examinedという感じ。ドライバーでもパッティングできるけどパターも使いこなせるとスコアアップ間違い無し💪</p> <div class="footnote"> <p class="footnote"><a href="#fn-a62cbb9c" name="f-a62cbb9c" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text"><a href="https://twitter.com/RDBMS/status/976955535076466688">https://twitter.com/RDBMS/status/976955535076466688</a></span></p> </div> kamipo ActiveRecordでINの中が一万個とかにならないようにする hatenablog://entry/8599973812329156599 2017-12-23T23:05:14+09:00 2019-04-27T06:41:00+09:00 この記事は MySQL Casual Advent Calendar 2017 の23日目の記事です。 みなさんORマッパーは使っていますか? 僕は仕事とか趣味でActiveRecordというORマッパーを使っているんですけど、こいつ例えば Team.preload(players: :high_score).to_a みたいなことをするとすぐ SELECT `scores`.* FROM `scores` FROM `scores`.`id` IN (a, b, c, ...数千個続く...) みたいなクエリを生成しよるんですけど、MySQL 5.7に上げたときに range_optimiz… <p>この記事は <a href="https://qiita.com/advent-calendar/2017/mysql-casual">MySQL Casual Advent Calendar 2017</a> の23日目の記事です。</p> <p>みなさんORマッパーは使っていますか?</p> <p>僕は仕事とか趣味で<a class="keyword" href="http://d.hatena.ne.jp/keyword/ActiveRecord">ActiveRecord</a>というORマッパーを使っているんですけど、こいつ例えば</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synType">Team</span>.preload(<span class="synConstant">players</span>: <span class="synConstant">:high_score</span>).to_a </pre> <p>みたいなことをするとすぐ</p> <pre class="code lang-sql" data-lang="sql" data-unlink><span class="synStatement">SELECT</span> `scores`.* <span class="synSpecial">FROM</span> `scores` <span class="synSpecial">FROM</span> `scores`.`id` <span class="synStatement">IN</span> (a, b, c, ...数千個続く...) </pre> <p>みたいなクエリを生成しよるんですけど、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7に上げたときに <a href="https://dev.mysql.com/doc/refman/5.7/en/range-optimization.html#range-optimization-memory-use">range_optimizer_max_mem_size</a> の制限で実行計画がテーブルスキャンに落ちてえらい目にあったことがありました。<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>側で <code>range_optimizer_max_mem_size = 0</code> することでこの制限を無くすことができますが、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>側の設定によらずアプリケーションが動作できるようINの中の個数を制限する方法をここでは考えます。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>以外のことはよくわからないんですが、どうやら<a class="keyword" href="http://d.hatena.ne.jp/keyword/Oracle">Oracle</a>にはINの中の個数が1000個までしか入れられないという制限があるようで、これに対応するために<a class="keyword" href="http://d.hatena.ne.jp/keyword/ActiveRecord">ActiveRecord</a>には <code>in_clause_length</code> の個数ずつにsliceしてクエリを投げる仕組みが元々存在します。<a class="keyword" href="http://d.hatena.ne.jp/keyword/Oracle">Oracle</a>以外には個数の制限はないんですけど、この値を上書きすると他のバックエンドでもINの中の個数を制限することができるわけです。例えば<a class="keyword" href="http://d.hatena.ne.jp/keyword/Oracle">Oracle</a>同様1000個までに制限したい場合は以下のようにすればよいです。</p> <pre class="code lang-ruby" data-lang="ruby" data-unlink><span class="synType">ActiveSupport</span>.on_load(<span class="synConstant">:active_record</span>) <span class="synStatement">do</span> <span class="synPreProc">module</span> <span class="synType">ActiveRecord</span> <span class="synPreProc">module</span> <span class="synType">ConnectionAdapters</span> <span class="synPreProc">module</span> <span class="synType">DatabaseLimitsExt</span> <span class="synPreProc">def</span> <span class="synIdentifier">in_clause_length</span> <span class="synConstant">1000</span> <span class="synPreProc">end</span> <span class="synPreProc">end</span> <span class="synPreProc">class</span> <span class="synType">AbstractAdapter</span> <span class="synPreProc">prepend</span> <span class="synType">DatabaseLimitsExt</span> <span class="synPreProc">end</span> <span class="synPreProc">end</span> <span class="synPreProc">end</span> <span class="synStatement">end</span> </pre> <p>もしみなさんのORマッパーがINの中が一万個とかになるクエリを生成しているなら <code>range_optimizer_max_mem_size</code> の制限に引っかかってないか確認してみるといいかもしれません。</p> <p>【追記】</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Rails">Rails</a> 6.0.0以降sliceしたINをひとつのクエリで投げるのでここで期待した効果は得られなくなりました。</p> <p><iframe src="https://hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F36074" title="Fix sliced IN clauses to be grouped by kamipo · Pull Request #36074 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/36074">github.com</a></cite></p> kamipo MySQL 8.0ではデフォルトで濁点半濁点を区別しなくなる hatenablog://entry/8599973812272614286 2017-06-22T00:59:58+09:00 2017-06-22T00:59:58+09:00 4月にMySQL 8.0のUnicodeと日本語対応についてManyi Luさんとディスカッションする会があって、かなりいろいろ話してとてもよい会だった。その後いろいろ考えて感じてる懸念を端的に書き記しておく。 デフォルトのcollationがutf8mb4_0900_ai_ciになった これに関して僕は強い懸念を持っている。MySQL 8.0以前において、ふつうのWebアプリケーションなどで日本語を扱う場合、実用上デフォルトのutf8mb4_general_ciかutf8mb4_binの2択であったと思う。デフォルトがutf8mb4_general_ciなので新しく作られるアプリケーションは… <p>4月に<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0の<a class="keyword" href="http://d.hatena.ne.jp/keyword/Unicode">Unicode</a>と日本語対応についてManyi Luさんとディスカッションする会があって、かなりいろいろ話してとてもよい会だった。その後いろいろ考えて感じてる懸念を端的に書き記しておく。</p> <h4>デフォルトのcollationがutf8mb4_0900_ai_ciになった</h4> <p>これに関して僕は強い懸念を持っている。<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0以前において、ふつうのWebアプリケーションなどで日本語を扱う場合、実用上デフォルトのutf8mb4_general_ciかutf8mb4_binの2択であったと思う。デフォルトがutf8mb4_general_ciなので新しく作られるアプリケーションは通常は濁点半濁点が区別される状態で世に出てくることになる。けど<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0.1のデフォルトのutf8mb4_0900_ai_ciは濁点半濁点を区別しないので、将来ユーザー名を登録するところでバイトさんが登録してたらハイドさんは登録できないみたいなことが今よりも多く起きるだろうと思われる。</p> <h4>どうなればいいと思っているか</h4> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0以前のutf8mb4のデフォルトのcollationであるutf8mb4_general_ciにはSMP文字(絵文字とか)を0xFFFD (REPLACEMENT CHARACTER)として比較するという問題がある。この挙動はdocumentedなので仕様だけど、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%BD%A4%E2%A4%BD%A4%E2">そもそも</a>2010年に<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.5 GAが出たときにこの挙動でutf8mb4を世に出してしまったことはこの際バグであったこととして、utf8mb4_general_ciのASCII範囲外の<a class="keyword" href="http://d.hatena.ne.jp/keyword/BMP">BMP</a>文字と同様<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CA%B8%BB%FA%A5%B3%A1%BC%A5%C9">文字コード</a>で比較するように修正するか修正した新たなcollationを用意して、デフォルトのcollationは修正版utf8mb4_general_ciであるほうが困る人が少ない選択だと思う。既存のcollationの挙動を変えてしまうと互換性の問題があるのだと思うけど(たとえば既存のすでに作られているインデックスは0xFFFDとして作られているところを<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CA%B8%BB%FA%A5%B3%A1%BC%A5%C9">文字コード</a>で探索はできない等)、個人的にはデフォルトのcollationをutf8mb4_0900_ai_ciに変えるのに比べて許容できる非互換だと思うしバグ修正といって差し支えないように思う。</p> <p>といった感じでデフォルトのcollationが変わることに関しての懸念を書きましたが、用途に応じて適切に設定すれば<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 8.0は21世紀最高の<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>になることをプロミスします!💫</p> <h4>SEE ALSO</h4> <ul> <li><a href="http://labs.gree.jp/blog/2017/04/16406/">&#x5BFF;&#x53F8;&#x3068;&#x30D3;&#x30FC;&#x30EB;&#x306B;&#x3064;&#x3044;&#x3066;&#x8A71;&#x3057;&#x5408;&#x3044;&#x3092;&#x3057;&#x3066;&#x304D;&#x307E;&#x3057;&#x305F; | GREE Engineers&#39; Blog</a><a href="http://b.hatena.ne.jp/entry/http://labs.gree.jp/blog/2017/04/16406/" class="http-bookmark"><img src="//b.hatena.ne.jp/entry/image/http://labs.gree.jp/blog/2017/04/16406/" alt="" class="http-bookmark" /></a></li> <li><a href="http://tmtms.hatenablog.com/entry/2017/06/19/mysql-ja-collation">MySQL&#x306E;&#x65E5;&#x672C;&#x8A9E;&#x30B3;&#x30EC;&#x30FC;&#x30B7;&#x30E7;&#x30F3; - @tmtms &#x306E;&#x30E1;&#x30E2;</a><a href="http://b.hatena.ne.jp/entry/http://tmtms.hatenablog.com/entry/2017/06/19/mysql-ja-collation" class="http-bookmark"><img src="//b.hatena.ne.jp/entry/image/http://tmtms.hatenablog.com/entry/2017/06/19/mysql-ja-collation" alt="" class="http-bookmark" /></a></li> </ul> kamipo MySQLでORDER BYをつけないときの並び順 hatenablog://entry/10328749687200967343 2016-12-24T23:49:44+09:00 2016-12-24T23:49:44+09:00 メリークリスマス!🎅🎄 このエントリはMySQL Casual Advent Calendar 2016の24日目です。 今日はこれの話です! @eagletmt 実装と実行計画依存です(たとえばInnoDBで単一カラムのインデックスが使われた場合のsort orderはprimary keyになるはずです)— Ryuta Kamizono (@kamipo) December 4, 2016 @eagletmt すいません、すこし間違いがありました。もし hoge_id = ? のような絞り込みで単一カラムのインデックスが採用された場合はsort orderはprimary keyになるはず… <p>メリークリスマス!🎅🎄</p> <p>このエントリは<a href="http://qiita.com/advent-calendar/2016/mysql-casual">MySQL Casual Advent Calendar 2016</a>の24日目です。</p> <p>今日はこれの話です!</p> <p><blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr"><a href="https://twitter.com/eagletmt">@eagletmt</a> 実装と実行計画依存です(たとえば<a class="keyword" href="http://d.hatena.ne.jp/keyword/InnoDB">InnoDB</a>で単一カラムのインデックスが使われた場合のsort orderはprimary keyになるはずです)</p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/805247073943580672">December 4, 2016</a></blockquote><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p> <p><blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr"><a href="https://twitter.com/eagletmt">@eagletmt</a> すいません、すこし間違いがありました。もし <a class="keyword" href="http://d.hatena.ne.jp/keyword/hoge">hoge</a>_id = ? のような絞り込みで単一カラムのインデックスが採用された場合はsort orderはprimary keyになるはずです。<a class="keyword" href="http://d.hatena.ne.jp/keyword/InnoDB">InnoDB</a>前提なら基本的に実行計画依存です。</p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/805248233555402753">December 4, 2016</a></blockquote><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p> <p><blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr"><a href="https://twitter.com/eagletmt">@eagletmt</a> <a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>でorder by無しのときの結果セットの並び順はその実装とその実行計画で自然に取り出せる順になるので、たとえば単一カラムインデックスでも <a class="keyword" href="http://d.hatena.ne.jp/keyword/hoge">hoge</a>_id IN (?,?) や OR を含むクエリだとprimary key順にならなくなります。</p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/805257816625643520">December 4, 2016</a></blockquote><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p> <h4>並び順が実装と実行計画に依存するとはどういうことか</h4> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>でORDER BYがついていないときに返す結果セットの並び順は定められていない。なのでORDER BYがついてないとき<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>はとくに何もせず自然に返せる順序で結果セットを返します。<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>はストレージエンジンがプラガブルになっていてストレージエンジン毎にデータ構造や実装が異なるので自然に返せる順序もストレージエンジン毎に異なりうる。それを確かめるために、<a class="keyword" href="http://d.hatena.ne.jp/keyword/InnoDB">InnoDB</a>と<a class="keyword" href="http://d.hatena.ne.jp/keyword/MyISAM">MyISAM</a>でORDER BYをつけないときの並び順がどう違うのか見てみましょう。</p> <pre class="code lang-mysql" data-lang="mysql" data-unlink><span class="synStatement">CREATE</span> <span class="synStatement">TABLE</span> myisam_casual_2016 ( <span class="synType">date</span> <span class="synType">date</span> <span class="synStatement">PRIMARY</span> <span class="synStatement">KEY</span>, author <span class="synType">varchar(</span><span class="synConstant">250</span><span class="synType">)</span> <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span>, title <span class="synType">varchar(</span><span class="synConstant">250</span><span class="synType">)</span> <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span>, <span class="synStatement">INDEX</span> idx_author (author) ) ENGINE=<span class="synStatement">MyISAM</span> CHARSET=utf8mb4; <span class="synStatement">CREATE</span> <span class="synStatement">TABLE</span> innodb_casual_2016 ( <span class="synType">date</span> <span class="synType">date</span> <span class="synStatement">PRIMARY</span> <span class="synStatement">KEY</span>, author <span class="synType">varchar(</span><span class="synConstant">250</span><span class="synType">)</span> <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span>, title <span class="synType">varchar(</span><span class="synConstant">250</span><span class="synType">)</span> <span class="synStatement">NOT</span> <span class="synSpecial">NULL</span>, <span class="synStatement">INDEX</span> idx_author (author) ) ENGINE=InnoDB CHARSET=utf8mb4; <span class="synStatement">INSERT</span> <span class="synStatement">INTO</span> myisam_casual_2016 <span class="synStatement">VALUES</span> (<span class="synConstant">'2016-12-24'</span>,<span class="synConstant">'kamipo'</span>,<span class="synConstant">'MySQLでORDER BYをつけないときの並び順'</span>), (<span class="synConstant">'2016-12-23'</span>,<span class="synConstant">'yoku0825'</span>,<span class="synConstant">'COUNTを速くする(?)SQL1本ノック その2'</span>), (<span class="synConstant">'2016-12-22'</span>,<span class="synConstant">'i_rethi'</span>,<span class="synConstant">'MySQL5.6と5.7のちょっとした違いとinnodb_thread_concurrencyの影響'</span>), (<span class="synConstant">'2016-12-21'</span>,<span class="synConstant">'atsuizo'</span>,<span class="synConstant">'お兄さんとの約束は守ろう!(tx_isolationとbinlogの話)'</span>), (<span class="synConstant">'2016-12-20'</span>,<span class="synConstant">'ogataka50'</span>,<span class="synConstant">'MySQL WorkbenchのVisual Explainでのみ出てくるaccess typeの話'</span>), (<span class="synConstant">'2016-12-19'</span>,<span class="synConstant">'yoku0825'</span>,<span class="synConstant">'COUNTを速くする(?)SQL1本ノック その1'</span>), (<span class="synConstant">'2016-12-18'</span>,<span class="synConstant">'atsuizo'</span>,<span class="synConstant">'MySQLのChar型のデータサイズについて小ネタ'</span>), (<span class="synConstant">'2016-12-17'</span>,<span class="synConstant">'yut148'</span>,<span class="synConstant">'upscaledb-mysqlを試す'</span>), (<span class="synConstant">'2016-12-16'</span>,<span class="synConstant">'yebihara'</span>,<span class="synConstant">'MySQLのMyRocksストレージエンジンの話を中の人から聞いた'</span>), (<span class="synConstant">'2016-12-15'</span>,<span class="synConstant">'eshimizu'</span>,<span class="synConstant">'ハゲたサンタがMySQLを覗きはじめたお話'</span>), (<span class="synConstant">'2016-12-14'</span>,<span class="synConstant">'mita2'</span>,<span class="synConstant">'MySQL 8.0 DMR と 5.7 パラメータ比較'</span>), (<span class="synConstant">'2016-12-13'</span>,<span class="synConstant">'RKajiyama'</span>,<span class="synConstant">'MySQL 5.7.17が出たのであのプラグインについて書いてみました'</span>), (<span class="synConstant">'2016-12-12'</span>,<span class="synConstant">'atsuizo'</span>,<span class="synConstant">'JOIN ON で絞り込み条件を入れるのと、JOIN ONの後WHERE句で絞り込み条件を入れるのとでは、結果が違う件'</span>), (<span class="synConstant">'2016-12-11'</span>,<span class="synConstant">'yoku0825'</span>,<span class="synConstant">'試される大地 YAPC::Hokkaido 2016 SapporoでMySQL 8.0の話をしてきた'</span>), (<span class="synConstant">'2016-12-10'</span>,<span class="synConstant">'yoku0825'</span>,<span class="synConstant">'MySQLのビルド環境にConoHaを選んでいる理由'</span>), (<span class="synConstant">'2016-12-09'</span>,<span class="synConstant">'meijik'</span>,<span class="synConstant">'MySQL 8.0 Lab版でCTE(Common Table Expression)'</span>), (<span class="synConstant">'2016-12-08'</span>,<span class="synConstant">'yoku0825'</span>,<span class="synConstant">'最近のMroongaさんの構成について'</span>), (<span class="synConstant">'2016-12-07'</span>,<span class="synConstant">'mita2'</span>,<span class="synConstant">'MySQL 8.0 新機能 Persisting configuration variables'</span>), (<span class="synConstant">'2016-12-06'</span>,<span class="synConstant">'atsuizo'</span>,<span class="synConstant">'SQLを繰り返し実行したら段階的に応答速度が上がった話'</span>), (<span class="synConstant">'2016-12-05'</span>,<span class="synConstant">'atsuizo'</span>,<span class="synConstant">'MySQLのトランザクション制御がキモい話'</span>), (<span class="synConstant">'2016-12-04'</span>,<span class="synConstant">'zurazurataicho'</span>,<span class="synConstant">'MySQL5.7でJSON型を試してみた'</span>), (<span class="synConstant">'2016-12-03'</span>,<span class="synConstant">'yoku0825'</span>,<span class="synConstant">'mysqlコマンドラインクライアントのコマンド集'</span>), (<span class="synConstant">'2016-12-02'</span>,<span class="synConstant">'kakuka4430'</span>,<span class="synConstant">'CentOS6.8にtpcc-mysqlを入れようとして失敗した話'</span>), (<span class="synConstant">'2016-12-01'</span>,<span class="synConstant">'yoku0825'</span>,<span class="synConstant">'MySQLのNOW関数はどのようにして安全にスレーブでリプレイされるのか'</span>); <span class="synStatement">INSERT</span> <span class="synStatement">INTO</span> innodb_casual_2016 <span class="synStatement">SELECT</span> * <span class="synStatement">FROM</span> myisam_casual_2016; </pre> <p>日付を主キーにして、日付の降順に初期データを投入しています。<a class="keyword" href="http://d.hatena.ne.jp/keyword/MyISAM">MyISAM</a>は基本的に追記型のデータ構造なので実行計画がテーブルスキャンのときの自然に返せる結果セットの順序はデータの投入順ということになります。一方<a class="keyword" href="http://d.hatena.ne.jp/keyword/InnoDB">InnoDB</a>のデータ構造は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AF%A5%E9%A5%B9%A5%BF">クラスタ</a>ードインデックスなので主キーを持つテーブルの場合データは主キーのキー値でソート済みになっています。</p> <pre class="code lang-mysql" data-lang="mysql" data-unlink>&gt; <span class="synStatement">SELECT</span> * <span class="synStatement">FROM</span> myisam_casual_2016 <span class="synStatement">USE</span> <span class="synStatement">INDEX</span> () <span class="synStatement">WHERE</span> author <span class="synStatement">IN</span> (<span class="synConstant">'yoku0825'</span>,<span class="synConstant">'atsuizo'</span>) <span class="synStatement">LIMIT</span> <span class="synConstant">10</span>; +------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------+ | <span class="synType">date</span> | author | title | +------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------+ | <span class="synConstant">2016-12-23</span> | yoku0825 | COUNTを速くする(?)SQL1本ノック その<span class="synConstant">2</span> | | <span class="synConstant">2016-12-21</span> | atsuizo | お兄さんとの約束は守ろう!(tx_isolationとbinlogの話) | | <span class="synConstant">2016-12-19</span> | yoku0825 | COUNTを速くする(?)SQL1本ノック その<span class="synConstant">1</span> | | <span class="synConstant">2016-12-18</span> | atsuizo | MySQLのChar型のデータサイズについて小ネタ | | <span class="synConstant">2016-12-12</span> | atsuizo | <span class="synStatement">JOIN</span> <span class="synStatement">ON</span> で絞り込み条件を入れるのと、<span class="synStatement">JOIN</span> ONの後WHERE句で絞り込み条件を入れるのとでは、結果が違う件 | | <span class="synConstant">2016-12-11</span> | yoku0825 | 試される大地 YAPC::Hokkaido <span class="synConstant">2016</span> SapporoでMySQL <span class="synConstant">8.0</span>の話をしてきた | | <span class="synConstant">2016-12-10</span> | yoku0825 | MySQLのビルド環境にConoHaを選んでいる理由 | | <span class="synConstant">2016-12-08</span> | yoku0825 | 最近のMroongaさんの構成について | | <span class="synConstant">2016-12-06</span> | atsuizo | SQLを繰り返し実行したら段階的に応答速度が上がった話 | | <span class="synConstant">2016-12-05</span> | atsuizo | MySQLのトランザクション制御がキモい話 | +------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------+ <span class="synConstant">10</span> <span class="synStatement">rows</span> <span class="synStatement">in</span> set (<span class="synConstant">0.00</span> sec) &gt; <span class="synStatement">SELECT</span> * <span class="synStatement">FROM</span> innodb_casual_2016 <span class="synStatement">USE</span> <span class="synStatement">INDEX</span> () <span class="synStatement">WHERE</span> author <span class="synStatement">IN</span> (<span class="synConstant">'yoku0825'</span>,<span class="synConstant">'atsuizo'</span>) <span class="synStatement">LIMIT</span> <span class="synConstant">10</span>; +------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------+ | <span class="synType">date</span> | author | title | +------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------+ | <span class="synConstant">2016-12-01</span> | yoku0825 | MySQLのNOW関数はどのようにして安全にスレーブでリプレイされるのか | | <span class="synConstant">2016-12-03</span> | yoku0825 | mysqlコマンドラインクライアントのコマンド集 | | <span class="synConstant">2016-12-05</span> | atsuizo | MySQLのトランザクション制御がキモい話 | | <span class="synConstant">2016-12-06</span> | atsuizo | SQLを繰り返し実行したら段階的に応答速度が上がった話 | | <span class="synConstant">2016-12-08</span> | yoku0825 | 最近のMroongaさんの構成について | | <span class="synConstant">2016-12-10</span> | yoku0825 | MySQLのビルド環境にConoHaを選んでいる理由 | | <span class="synConstant">2016-12-11</span> | yoku0825 | 試される大地 YAPC::Hokkaido <span class="synConstant">2016</span> SapporoでMySQL <span class="synConstant">8.0</span>の話をしてきた | | <span class="synConstant">2016-12-12</span> | atsuizo | <span class="synStatement">JOIN</span> <span class="synStatement">ON</span> で絞り込み条件を入れるのと、<span class="synStatement">JOIN</span> ONの後WHERE句で絞り込み条件を入れるのとでは、結果が違う件 | | <span class="synConstant">2016-12-18</span> | atsuizo | MySQLのChar型のデータサイズについて小ネタ | | <span class="synConstant">2016-12-19</span> | yoku0825 | COUNTを速くする(?)SQL1本ノック その<span class="synConstant">1</span> | +------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------+ <span class="synConstant">10</span> <span class="synStatement">rows</span> <span class="synStatement">in</span> set (<span class="synConstant">0.00</span> sec) </pre> <p>セカンダリキーを使う実行計画では当然セカンダリキーのキー値でソートされた結果セットを返しますが、同じキー値同士のレコードの順序は<a class="keyword" href="http://d.hatena.ne.jp/keyword/MyISAM">MyISAM</a>と<a class="keyword" href="http://d.hatena.ne.jp/keyword/InnoDB">InnoDB</a>で異なります。ここでも<a class="keyword" href="http://d.hatena.ne.jp/keyword/MyISAM">MyISAM</a>はデータの投入順、<a class="keyword" href="http://d.hatena.ne.jp/keyword/InnoDB">InnoDB</a>は主キーのキー値の順序になります。</p> <pre class="code lang-mysql" data-lang="mysql" data-unlink>&gt; <span class="synStatement">SELECT</span> * <span class="synStatement">FROM</span> myisam_casual_2016 <span class="synStatement">USE</span> <span class="synStatement">INDEX</span> (idx_author) <span class="synStatement">WHERE</span> author <span class="synStatement">IN</span> (<span class="synConstant">'yoku0825'</span>,<span class="synConstant">'atsuizo'</span>) <span class="synStatement">LIMIT</span> <span class="synConstant">10</span>; +------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------+ | <span class="synType">date</span> | author | title | +------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------+ | <span class="synConstant">2016-12-21</span> | atsuizo | お兄さんとの約束は守ろう!(tx_isolationとbinlogの話) | | <span class="synConstant">2016-12-18</span> | atsuizo | MySQLのChar型のデータサイズについて小ネタ | | <span class="synConstant">2016-12-12</span> | atsuizo | <span class="synStatement">JOIN</span> <span class="synStatement">ON</span> で絞り込み条件を入れるのと、<span class="synStatement">JOIN</span> ONの後WHERE句で絞り込み条件を入れるのとでは、結果が違う件 | | <span class="synConstant">2016-12-06</span> | atsuizo | SQLを繰り返し実行したら段階的に応答速度が上がった話 | | <span class="synConstant">2016-12-05</span> | atsuizo | MySQLのトランザクション制御がキモい話 | | <span class="synConstant">2016-12-23</span> | yoku0825 | COUNTを速くする(?)SQL1本ノック その<span class="synConstant">2</span> | | <span class="synConstant">2016-12-19</span> | yoku0825 | COUNTを速くする(?)SQL1本ノック その<span class="synConstant">1</span> | | <span class="synConstant">2016-12-11</span> | yoku0825 | 試される大地 YAPC::Hokkaido <span class="synConstant">2016</span> SapporoでMySQL <span class="synConstant">8.0</span>の話をしてきた | | <span class="synConstant">2016-12-10</span> | yoku0825 | MySQLのビルド環境にConoHaを選んでいる理由 | | <span class="synConstant">2016-12-08</span> | yoku0825 | 最近のMroongaさんの構成について | +------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------+ <span class="synConstant">10</span> <span class="synStatement">rows</span> <span class="synStatement">in</span> set (<span class="synConstant">0.00</span> sec) &gt; <span class="synStatement">SELECT</span> * <span class="synStatement">FROM</span> innodb_casual_2016 <span class="synStatement">USE</span> <span class="synStatement">INDEX</span> (idx_author) <span class="synStatement">WHERE</span> author <span class="synStatement">IN</span> (<span class="synConstant">'yoku0825'</span>,<span class="synConstant">'atsuizo'</span>) <span class="synStatement">LIMIT</span> <span class="synConstant">10</span>; +------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------+ | <span class="synType">date</span> | author | title | +------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------+ | <span class="synConstant">2016-12-05</span> | atsuizo | MySQLのトランザクション制御がキモい話 | | <span class="synConstant">2016-12-06</span> | atsuizo | SQLを繰り返し実行したら段階的に応答速度が上がった話 | | <span class="synConstant">2016-12-12</span> | atsuizo | <span class="synStatement">JOIN</span> <span class="synStatement">ON</span> で絞り込み条件を入れるのと、<span class="synStatement">JOIN</span> ONの後WHERE句で絞り込み条件を入れるのとでは、結果が違う件 | | <span class="synConstant">2016-12-18</span> | atsuizo | MySQLのChar型のデータサイズについて小ネタ | | <span class="synConstant">2016-12-21</span> | atsuizo | お兄さんとの約束は守ろう!(tx_isolationとbinlogの話) | | <span class="synConstant">2016-12-01</span> | yoku0825 | MySQLのNOW関数はどのようにして安全にスレーブでリプレイされるのか | | <span class="synConstant">2016-12-03</span> | yoku0825 | mysqlコマンドラインクライアントのコマンド集 | | <span class="synConstant">2016-12-08</span> | yoku0825 | 最近のMroongaさんの構成について | | <span class="synConstant">2016-12-10</span> | yoku0825 | MySQLのビルド環境にConoHaを選んでいる理由 | | <span class="synConstant">2016-12-11</span> | yoku0825 | 試される大地 YAPC::Hokkaido <span class="synConstant">2016</span> SapporoでMySQL <span class="synConstant">8.0</span>の話をしてきた | +------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------+ <span class="synConstant">10</span> <span class="synStatement">rows</span> <span class="synStatement">in</span> set (<span class="synConstant">0.00</span> sec) </pre> <h4>ORDER BY狙いのキーとはなんだったのか</h4> <p><blockquote class="twitter-tweet" data-lang="en"><p lang="ja" dir="ltr">No Using temporary; No Using filesort の言い換えではあるけどカーディナリティが高くてもUNIQUEなキーじゃない限り基本的にデータは単調増加するのでどういう順番でインデックスレコードに触れてどういう順番でデータを返したいのかを一番気にしてる。</p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/701900696983965696">February 22, 2016</a></blockquote><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script><cite class="hatena-citation"><a href="https://twitter.com/kamipo/statuses/701900696983965696">twitter.com</a></cite></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>でOLTP系クエリのチューニングをすることはつまるところ No using temporary, No using filesort を狙いに行くことですが、 No using filesort を狙うというのは欲しい結果セットの順序と<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>が自然に返せる結果セットの順序を合わせるように実行計画を選択するということに他ならないので、もし<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>の気持ちが分からなくなったときは、結果セットの順序に思いを馳せてみるのも良いかもしれません。</p> kamipo ISUCON6予選にチーム「それぞれの椅子」で参加した hatenablog://entry/10328749687184977466 2016-09-18T18:29:41+09:00 2016-09-18T18:56:26+09:00 ISUCON6予選1日目にチーム「それぞれの椅子(kamipo, Yappo, kan)」で参加した(kanさんは予定があってリモートからの友情出演)。 結果からいうとスコア15万ぐらいで安定したとこでもう時間ないから触るのやめて再起動チェックだけやって終わろうって再起動したら3万ぐらいまでスコア下がって原因特定するには時間なさすぎて死んだ(最後6万ぐらいまでは回復したっぽい)。俺の屍を越えてゆく者へ言えることは、不測の事態にそなえて再起動チェックは時間に余裕をもって何度かやるべきということです。 結果は残念だったけど今回はとても楽しめた。これまでのISUCONではせっかく声をかけて集まっても… <p>ISUCON6予選1日目にチーム「それぞれの椅子(kamipo, Yappo, kan)」で参加した(kanさんは予定があってリモートからの友情出演)。</p> <p>結果からいうとスコア15万ぐらいで安定したとこでもう時間ないから触るのやめて再起動チェックだけやって終わろうって再起動したら3万ぐらいまでスコア下がって原因特定するには時間なさすぎて死んだ(最後6万ぐらいまでは回復したっぽい)。俺の屍を越えてゆく者へ言えることは、不測の事態にそなえて再起動チェックは時間に余裕をもって何度かやるべきということです。</p> <p>結果は残念だったけど今回はとても楽しめた。これまでのISUCONではせっかく声をかけて集まってもらったのだからみんなのパフォーマンスを引き出さなければというプレッシャーがハンパなかったけど、みんな大人なんだから自分のパフォーマンスぐらい自分で発揮するやろって気持ちでやれたのがよかった。やっぽさん当日の朝6時ぐらいまで飲んでたけどみそ汁飲んだら復活してたし。</p> <p>今回のお題は<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%CF%A4%C6%A4%CA%A5%C0%A5%A4%A5%A2%A5%EA%A1%BC">はてなダイアリー</a>を模した<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AD%A1%BC%A5%EF%A1%BC%A5%C9%A5%EA%A5%F3%A5%AF">キーワードリンク</a>するWebアプリ(+<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A4%CF%A4%C6%A4%CA%A5%B9%A5%BF%A1%BC">はてなスター</a>とスパム判定が別サービスで立ってるマイクロサービス構成)。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AD%A1%BC%A5%EF%A1%BC%A5%C9%A5%EA%A5%F3%A5%AF">キーワードリンク</a>したHTMLを生成する処理が<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DC%A5%C8%A5%EB%A5%CD%A5%C3%A5%AF">ボトルネック</a>のほぼすべてといっていいお題で、この処理をいかに高速化するか(もしくはオフロードするか)という問題だと理解しました。<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AD%A1%BC%A5%EF%A1%BC%A5%C9%A5%EA%A5%F3%A5%AF">キーワードリンク</a>は登録されているエントリ名(keyword)の最長マッチが期待されていて、チェッカーは既存のエントリ名をプレフィックスにもつkeywordをPOSTしてきて最長マッチが正しく考慮されているかをチェックしているようでした。</p> <p>まず我がチームのとった戦略はキーワードマッチのための<a class="keyword" href="http://d.hatena.ne.jp/keyword/regexp">regexp</a>生成を<a class="keyword" href="http://d.hatena.ne.jp/keyword/Regexp">Regexp</a>::Assembleを使う&キャッシュすること。これで3万ぐらいまではいったけど、ここからどう速くするかは結構悩んだ。他に多少無駄なところ(高速化の余地)があっても<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AD%A1%BC%A5%EF%A1%BC%A5%C9%A5%EA%A5%F3%A5%AF">キーワードリンク</a>生成を速くできる目処が立たない限り効果が誤差レベルなので。</p> <p>この<a class="keyword" href="http://d.hatena.ne.jp/keyword/regexp">regexp</a>生成のキャッシュ時に確認できたことは、キーワードの最長マッチは常に正確じゃなくてもチェッカーがPOSTしてくる既存のkeywordより長いものが考慮されていれば減点はないということ。そこで最終的には、初期状態からある既存のkeywordに対して置換の前処理(html_escapeの前)をキャッシュすることですべてのページの<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%AD%A1%BC%A5%EF%A1%BC%A5%C9%A5%EA%A5%F3%A5%AF">キーワードリンク</a>処理を高速化し、チェッカーに怒られるkeywordは後で個別に対応するという戦略でキャッシュいれてみたら予想通りスコア上がって15万ぐらいで安定するようになった。残り時間がもう30分を切ってたので個別対応を入れるのは諦めて最後に再起動チェックだけしようって再起動したらスコア3万。原因特定をするにも残り10分切ってたのでenqueueガチャで最後少し回復させるのが限界でフィニッシュ。</p> <p>再起動チェックする時間が足りなかったのは今後の教訓として、最後まで楽しく取り組めたのは自分の心構え以上に、<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DC%A5%C8%A5%EB%A5%CD%A5%C3%A5%AF">ボトルネック</a>はわかってるのに高速化する方法が簡単じゃない良い問題だったからだと思う。また、ISUCON5予選と違ってデータベースに<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%DC%A5%C8%A5%EB%A5%CD%A5%C3%A5%AF">ボトルネック</a>を寄せていないことは今回学生枠が多いISUCON6にとっても取り組みやすい問題だったのではないかと思います。</p> <p>とりあえずいつでも飲みにいけます🍺🍶</p> <p><span itemscope itemtype="http://schema.org/Photograph"><img src="https://cdn-ak.f.st-hatena.com/images/fotolife/k/kamipo/20160918/20160918181234.png" alt="f:id:kamipo:20160918181234p:plain" title="f:id:kamipo:20160918181234p:plain" class="hatena-fotolife" itemprop="image"></span></p> <p>入れる時間がなかった人の手による温かみのある個別対応</p> <p><a href="https://github.com/kamipo/isucon6q/compare/exclude_keyword">Comparing master...exclude_keyword &middot; kamipo/isucon6q &middot; GitHub</a></p> kamipo MySQL 5.7のONLY_FULL_GROUP_BYはちょっと進化してた hatenablog://entry/6653586347148542143 2015-12-14T17:18:38+09:00 2015-12-14T17:18:38+09:00 このエントリはMySQL Casual Advent Calendar 2015の14日目です。 TL;DR MySQL 5.7ではデフォルトONLY_FULL_GROUP_BYが有効である。MySQL 5.7.5からONLY_FULL_GROUP_BYが有効のとき GROUP BY句のカラムと関数従属性のあるカラムはSELECT句に書けるようになった😤 ORDER BY句のカラムはDISTINCTのカラムリストに含めなければいけなくなった😣 ONLY_FULL_GROUP_BYを無効にしなくてもHAVING句のalias拡張が使えるようになった😆 GROUP BY句のカラムと関数従属性のある… <p>このエントリは<a href="http://qiita.com/advent-calendar/2015/mysql-casual">MySQL Casual Advent Calendar 2015</a>の14日目です。</p> <p><strong>TL;DR</strong></p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7ではデフォルト<code>ONLY_FULL_GROUP_BY</code>が有効である。<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7.5から<code>ONLY_FULL_GROUP_BY</code>が有効のとき</p> <ul> <li>GROUP BY句のカラムと関数従属性のあるカラムはSELECT句に書けるようになった😤</li> <li>ORDER BY句のカラムはDISTINCTのカラムリストに含めなければいけなくなった😣</li> <li><code>ONLY_FULL_GROUP_BY</code>を無効にしなくてもHAVING句のalias拡張が使えるようになった😆</li> </ul> <h4>GROUP BY句のカラムと関数従属性のあるカラムはSELECT句に書けるようになった</h4> <pre class="code lang-mysql" data-lang="mysql" data-unlink>[mysqlcasual] &gt; <span class="synStatement">CREATE</span> <span class="synStatement">TABLE</span> users (id <span class="synType">int</span> <span class="synStatement">unsigned</span> <span class="synStatement">auto_increment</span> <span class="synStatement">primary</span> <span class="synStatement">key</span>, name <span class="synType">varchar(</span><span class="synConstant">255</span><span class="synType">)</span>); Query OK, <span class="synConstant">0</span> <span class="synStatement">rows</span> affected (<span class="synConstant">0.03</span> sec) [mysqlcasual] &gt; <span class="synStatement">CREATE</span> <span class="synStatement">TABLE</span> friends (user_id <span class="synType">int</span> <span class="synStatement">unsigned</span>, friend_id <span class="synType">int</span> <span class="synStatement">unsigned</span>); Query OK, <span class="synConstant">0</span> <span class="synStatement">rows</span> affected (<span class="synConstant">0.04</span> sec) </pre> <p>以下のクエリは<code>u.name</code>がGROUP BY句に含まれてなくて集合関数にもかけられてないけど<code>f.user_id</code>に関数従属しているので5.7では実行できます!</p> <pre class="code lang-mysql" data-lang="mysql" data-unlink>[mysqlcasual] &gt; <span class="synStatement">SELECT</span> f.user_id, u.name, <span class="synIdentifier">COUNT(*)</span> <span class="synStatement">FROM</span> friends f <span class="synStatement">JOIN</span> users u <span class="synStatement">ON</span> f.user_id = u.id <span class="synStatement">GROUP</span> <span class="synStatement">BY</span> f.user_id; Empty set (<span class="synConstant">0.00</span> sec) </pre> <p>残念ながら5.6では<code>ONLY_FULL_GROUP_BY</code>が有効だとエラーです。</p> <pre class="code lang-mysql" data-lang="mysql" data-unlink>[mysqlcasual] &gt; <span class="synStatement">SELECT</span> f.user_id, u.name, <span class="synIdentifier">COUNT(*)</span> <span class="synStatement">FROM</span> friends f <span class="synStatement">JOIN</span> users u <span class="synStatement">ON</span> f.user_id = u.id <span class="synStatement">GROUP</span> <span class="synStatement">BY</span> f.user_id; ERROR <span class="synConstant">1055</span> (<span class="synConstant">42000</span>): <span class="synConstant">'mysqlcasual.u.name'</span> isn<span class="synConstant">'t in GROUP BY</span> </pre> <h4>ORDER BY句のカラムはDISTINCTのカラムリストに含めなければいけなくなった</h4> <p>とにかくそういうことらしいです。他のが便利になる系なのに対してこれは既存のクエリに影響が出る系なので5.7にアップグレードして既存のクエリに影響があるか確認しておく必要があります。ちなみに<a class="keyword" href="http://d.hatena.ne.jp/keyword/ActiveRecord">ActiveRecord</a>というORマッパーは余裕でDISTINCTついてるクエリにORDER BYつくので滅茶苦茶影響がある。とにかくこれを一日も早くマージしてほしい。</p> <p><iframe src="//hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2Frails%2Frails%2Fpull%2F22241" title="Add `columns_for_distinct` for MySQL 5.7 with ONLY_FULL_GROUP_BY by kamipo · Pull Request #22241 · rails/rails" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/rails/rails/pull/22241">github.com</a></cite></p> <h4><code>ONLY_FULL_GROUP_BY</code>を無効にしなくてもHAVING句のalias拡張が使えるようになった</h4> <p>これもとにかくそういうことです!</p> <pre class="code lang-mysql" data-lang="mysql" data-unlink>[mysqlcasual] &gt; <span class="synStatement">SELECT</span> user_id, <span class="synIdentifier">COUNT(*)</span> c <span class="synStatement">FROM</span> friends <span class="synStatement">GROUP</span> <span class="synStatement">BY</span> user_id <span class="synStatement">HAVING</span> c = <span class="synConstant">1</span>; Empty set (<span class="synConstant">0.00</span> sec) </pre> <p>以上、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7の<code>ONLY_FULL_GROUP_BY</code>はちょっと進化してました!</p> <ul> <li><a href="https://dev.mysql.com/doc/refman/5.7/en/group-by-handling.html">MySQL 5.7 Reference Manual :: 12.20.3 MySQL Handling of GROUP BY</a></li> </ul> kamipo MySQL 5.7のoptimizer_switch、derived_mergeとは何ぞや hatenablog://entry/6653586347147818574 2015-12-08T07:58:00+09:00 2015-12-08T08:02:08+09:00 このエントリはMySQL Casual Advent Calendar 2015の8日目です。 MySQL 5.7.6からoptimizer_switchにderived_mergeが追加されデフォルトで有効になっている。基本的にこれはほっといたらだいたいサブクエリが速くなるやつなので気にしなくてもいいんですが、ちょっと非互換があるのでさくっと説明します。 root@localhost [mysqlcasual] > CREATE TABLE t1 (a int); Query OK, 0 rows affected (0.03 sec) root@localhost [mysqlcasual… <p>このエントリは<a href="http://qiita.com/advent-calendar/2015/mysql-casual">MySQL Casual Advent Calendar 2015</a>の8日目です。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7.6から<code>optimizer_switch</code>に<code>derived_merge</code>が追加されデフォルトで有効になっている。基本的にこれはほっといたらだいたいサブクエリが速くなるやつなので気にしなくてもいいんですが、ちょっと非互換があるのでさくっと説明します。</p> <pre class="code lang-mysql" data-lang="mysql" data-unlink>root<span class="synIdentifier">@localhost</span> [mysqlcasual] &gt; <span class="synStatement">CREATE</span> <span class="synStatement">TABLE</span> t1 (a <span class="synType">int</span>); Query OK, <span class="synConstant">0</span> <span class="synStatement">rows</span> affected (<span class="synConstant">0.03</span> sec) root<span class="synIdentifier">@localhost</span> [mysqlcasual] &gt; <span class="synStatement">CREATE</span> <span class="synStatement">TABLE</span> t2 (b <span class="synType">int</span>); Query OK, <span class="synConstant">0</span> <span class="synStatement">rows</span> affected (<span class="synConstant">0.03</span> sec) root<span class="synIdentifier">@localhost</span> [mysqlcasual] &gt; <span class="synStatement">INSERT</span> <span class="synStatement">INTO</span> t1 <span class="synStatement">VALUES</span> (<span class="synConstant">1</span>),(<span class="synConstant">2</span>),(<span class="synConstant">3</span>),(<span class="synConstant">4</span>),(<span class="synConstant">5</span>); Query OK, <span class="synConstant">5</span> <span class="synStatement">rows</span> affected (<span class="synConstant">0.02</span> sec) Records: <span class="synConstant">5</span> Duplicates: <span class="synConstant">0</span> Warnings: <span class="synConstant">0</span> root<span class="synIdentifier">@localhost</span> [mysqlcasual] &gt; <span class="synStatement">INSERT</span> <span class="synStatement">INTO</span> t2 <span class="synStatement">VALUES</span> (<span class="synConstant">1</span>),(<span class="synConstant">2</span>),(<span class="synConstant">3</span>),(<span class="synConstant">4</span>),(<span class="synConstant">5</span>); Query OK, <span class="synConstant">5</span> <span class="synStatement">rows</span> affected (<span class="synConstant">0.01</span> sec) Records: <span class="synConstant">5</span> Duplicates: <span class="synConstant">0</span> Warnings: <span class="synConstant">0</span> </pre> <p>これまでFROM句のサブクエリは一旦マテ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%A2%A5%E9%A5%A4%A5%BA">リアライズ</a>されてから外側のクエリとかWHERE句とかと結合されていた。</p> <pre class="code lang-mysql" data-lang="mysql" data-unlink>root<span class="synIdentifier">@localhost</span> [mysqlcasual] &gt; SET optimizer_switch = <span class="synConstant">'derived_merge=off'</span>; Query OK, <span class="synConstant">0</span> <span class="synStatement">rows</span> affected (<span class="synConstant">0.00</span> sec) root<span class="synIdentifier">@localhost</span> [mysqlcasual] &gt; <span class="synStatement">EXPLAIN</span> <span class="synStatement">SELECT</span> * <span class="synStatement">FROM</span> t1 <span class="synStatement">JOIN</span> (<span class="synStatement">SELECT</span> * <span class="synStatement">FROM</span> t2) dt; +----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+ | id | select_type | <span class="synStatement">table</span> | partitions | <span class="synStatement">type</span> | possible_keys | <span class="synStatement">key</span> | key_len | ref | <span class="synStatement">rows</span> | filtered | Extra | +----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+ | <span class="synConstant">1</span> | <span class="synStatement">PRIMARY</span> | t1 | <span class="synSpecial">NULL</span> | <span class="synStatement">ALL</span> | <span class="synSpecial">NULL</span> | <span class="synSpecial">NULL</span> | <span class="synSpecial">NULL</span> | <span class="synSpecial">NULL</span> | <span class="synConstant">5</span> | <span class="synConstant">100.00</span> | <span class="synSpecial">NULL</span> | | <span class="synConstant">1</span> | <span class="synStatement">PRIMARY</span> | &lt;derived2&gt; | <span class="synSpecial">NULL</span> | <span class="synStatement">ALL</span> | <span class="synSpecial">NULL</span> | <span class="synSpecial">NULL</span> | <span class="synSpecial">NULL</span> | <span class="synSpecial">NULL</span> | <span class="synConstant">5</span> | <span class="synConstant">100.00</span> | <span class="synStatement">Using</span> <span class="synStatement">join</span> buffer (Block Nested Loop) | | <span class="synConstant">2</span> | DERIVED | t2 | <span class="synSpecial">NULL</span> | <span class="synStatement">ALL</span> | <span class="synSpecial">NULL</span> | <span class="synSpecial">NULL</span> | <span class="synSpecial">NULL</span> | <span class="synSpecial">NULL</span> | <span class="synConstant">5</span> | <span class="synConstant">100.00</span> | <span class="synSpecial">NULL</span> | +----+-------------+------------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+ <span class="synConstant">3</span> <span class="synStatement">rows</span> <span class="synStatement">in</span> set, <span class="synConstant">1</span> warning (<span class="synConstant">0.00</span> sec) </pre> <p>これが<code>derived_merge=on</code>だとマテ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%A2%A5%E9%A5%A4%A5%BA">リアライズ</a>せずに外側の条件とマージできそうなときはマージされるようになるのだ!</p> <pre class="code lang-mysql" data-lang="mysql" data-unlink>root<span class="synIdentifier">@localhost</span> [mysqlcasual] &gt; SET optimizer_switch = <span class="synConstant">'derived_merge=on'</span>; Query OK, <span class="synConstant">0</span> <span class="synStatement">rows</span> affected (<span class="synConstant">0.00</span> sec) root<span class="synIdentifier">@localhost</span> [mysqlcasual] &gt; <span class="synStatement">EXPLAIN</span> <span class="synStatement">SELECT</span> * <span class="synStatement">FROM</span> t1 <span class="synStatement">JOIN</span> (<span class="synStatement">SELECT</span> * <span class="synStatement">FROM</span> t2) dt; +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+ | id | select_type | <span class="synStatement">table</span> | partitions | <span class="synStatement">type</span> | possible_keys | <span class="synStatement">key</span> | key_len | ref | <span class="synStatement">rows</span> | filtered | Extra | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+ | <span class="synConstant">1</span> | SIMPLE | t1 | <span class="synSpecial">NULL</span> | <span class="synStatement">ALL</span> | <span class="synSpecial">NULL</span> | <span class="synSpecial">NULL</span> | <span class="synSpecial">NULL</span> | <span class="synSpecial">NULL</span> | <span class="synConstant">5</span> | <span class="synConstant">100.00</span> | <span class="synSpecial">NULL</span> | | <span class="synConstant">1</span> | SIMPLE | t2 | <span class="synSpecial">NULL</span> | <span class="synStatement">ALL</span> | <span class="synSpecial">NULL</span> | <span class="synSpecial">NULL</span> | <span class="synSpecial">NULL</span> | <span class="synSpecial">NULL</span> | <span class="synConstant">5</span> | <span class="synConstant">100.00</span> | <span class="synStatement">Using</span> <span class="synStatement">join</span> buffer (Block Nested Loop) | +----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+---------------------------------------+ <span class="synConstant">2</span> <span class="synStatement">rows</span> <span class="synStatement">in</span> set, <span class="synConstant">1</span> warning (<span class="synConstant">0.00</span> sec) </pre> <p>これでサブクエリが多い日も安心!!</p> <p>しかしその代償に以下の更新クエリが通らなくなった。</p> <pre class="code lang-mysql" data-lang="mysql" data-unlink>root<span class="synIdentifier">@localhost</span> [mysqlcasual] &gt; <span class="synStatement">UPDATE</span> t2 SET b=<span class="synConstant">1</span> <span class="synStatement">WHERE</span> b <span class="synStatement">IN</span> (<span class="synStatement">SELECT</span> b <span class="synStatement">FROM</span> (<span class="synStatement">SELECT</span> * <span class="synStatement">FROM</span> t2) dt <span class="synStatement">WHERE</span> b=<span class="synConstant">1</span>); ERROR <span class="synConstant">1093</span> (HY000): You can<span class="synConstant">'t specify target table '</span>t2<span class="synConstant">' for update in FROM clause</span> </pre> <p>どうも<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>にはひとつの更新系クエリ(<code>UPDATE</code>, <code>DELETE</code>)で更新するテーブルと同じテーブルをFROM句の中で両方同時に参照できないという制約があるらしく、いままでマテ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%A2%A5%E9%A5%A4%A5%BA">リアライズ</a>されて別テーブルになってたから通ってたクエリが最適化によってマージされてそのまま参照されることでこの制約に引っかかるようになるみたいです。</p> <p>回避策としては、<code>derived_merge=off</code>にするか、サブクエリをマージできない(マテ<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%EA%A5%A2%A5%E9%A5%A4%A5%BA">リアライズ</a>される)クエリに書き換えるとよいです(<code>DISTINCT</code>や<code>LIMIT</code>をつけるとマージできなくなる)。</p> <pre class="code lang-mysql" data-lang="mysql" data-unlink>root<span class="synIdentifier">@localhost</span> [mysqlcasual] &gt; <span class="synStatement">UPDATE</span> t2 SET b=<span class="synConstant">1</span> <span class="synStatement">WHERE</span> b <span class="synStatement">IN</span> (<span class="synStatement">SELECT</span> b <span class="synStatement">FROM</span> (<span class="synStatement">SELECT</span> <span class="synStatement">DISTINCT</span> * <span class="synStatement">FROM</span> t2) dt <span class="synStatement">WHERE</span> b=<span class="synConstant">1</span>); Query OK, <span class="synConstant">0</span> <span class="synStatement">rows</span> affected (<span class="synConstant">0.00</span> sec) <span class="synStatement">Rows</span> matched: <span class="synConstant">1</span> Changed: <span class="synConstant">0</span> Warnings: <span class="synConstant">0</span> root<span class="synIdentifier">@localhost</span> [mysqlcasual] &gt; SET optimizer_switch = <span class="synConstant">'derived_merge=off'</span>; Query OK, <span class="synConstant">0</span> <span class="synStatement">rows</span> affected (<span class="synConstant">0.00</span> sec) root<span class="synIdentifier">@localhost</span> [mysqlcasual] &gt; <span class="synStatement">UPDATE</span> t2 SET b=<span class="synConstant">1</span> <span class="synStatement">WHERE</span> b <span class="synStatement">IN</span> (<span class="synStatement">SELECT</span> b <span class="synStatement">FROM</span> (<span class="synStatement">SELECT</span> * <span class="synStatement">FROM</span> t2) dt <span class="synStatement">WHERE</span> b=<span class="synConstant">1</span>); Query OK, <span class="synConstant">0</span> <span class="synStatement">rows</span> affected (<span class="synConstant">0.00</span> sec) <span class="synStatement">Rows</span> matched: <span class="synConstant">1</span> Changed: <span class="synConstant">0</span> Warnings: <span class="synConstant">0</span> </pre> <p>じつはVIEWもderived tableの仲間なので、<code>ALGORITHM=MERGE</code>なVIEWもこの制約に引っかかるので注意です。</p> <ul> <li><a href="https://dev.mysql.com/doc/refman/5.6/ja/view-algorithms.html">MySQL 5.6 リファレンスマニュアル :: 20.5.2 ビュー処理アルゴリズム</a></li> </ul> kamipo HomebrewのMySQL 5.7.9でSSL接続するとコケるので気をつけよう hatenablog://entry/6653586347147253523 2015-12-03T05:43:45+09:00 2015-12-03T05:43:45+09:00 このエントリはMySQL Casual Advent Calendar 2015の3日目です。 で、これです。 github.com ざっと調べた感じだと、openssl 1.0.1eあたりからDiffie-Hellman (DH) key length 1024bit以上を要求するようになったかなんかで、MySQLが512bitとかいうかなり短いkey lengthを使ってるせいでopensslに怒られる感じらしい。 MySQL 5.7からデフォルトsslオプションが有効なのでRDSとかみたいなSSL接続受け付けてるホストに5.7のクライアントでそらで接続しようとするとコケるっぽい。 $ m… <p>このエントリは<a href="http://qiita.com/advent-calendar/2015/mysql-casual">MySQL Casual Advent Calendar 2015</a>の3日目です。</p> <p>で、これです。</p> <p><iframe src="//hatenablog-parts.com/embed?url=https%3A%2F%2Fgithub.com%2FHomebrew%2Fhomebrew%2Fissues%2F46055" title="MySQL 5.7.9 can&#39;t connect to server · Issue #46055 · Homebrew/homebrew" class="embed-card embed-webcard" scrolling="no" frameborder="0" style="display: block; width: 100%; height: 155px; max-width: 500px; margin: 10px 0px;"></iframe><cite class="hatena-citation"><a href="https://github.com/Homebrew/homebrew/issues/46055">github.com</a></cite></p> <p>ざっと調べた感じだと、openssl 1.0.1eあたりから<a class="keyword" href="http://d.hatena.ne.jp/keyword/Diffie-Hellman">Diffie-Hellman</a> (DH) key length 1024bit以上を要求するようになったかなんかで、<a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>が512bitとかいうかなり短いkey lengthを使ってるせいでopensslに怒られる感じらしい。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> 5.7からデフォルト<a class="keyword" href="http://d.hatena.ne.jp/keyword/ssl">ssl</a>オプションが有効なのでRDSとかみたいな<a class="keyword" href="http://d.hatena.ne.jp/keyword/SSL">SSL</a>接続受け付けてるホストに5.7のクライアントでそらで接続しようとするとコケるっぽい。</p> <pre class="code" data-lang="" data-unlink>$ mysql --help ... --ssl If set to ON, this option enforces that SSL is established before client attempts to authenticate to the server. To disable client SSL capabilities use --ssl=OFF. (Defaults to on; use --skip-ssl to disable.) ...</pre> <p><a href="https://bugs.mysql.com/bug.php?id=77275">https://bugs.mysql.com/bug.php?id=77275</a> を見ると <a href="https://github.com/mysql/mysql-server/commit/866b988a76e8e7e217017a7883a52a12ec5024b9">mysql/mysql-server/commit/866b988</a> で直した的なことが書いてあるけど、Homebrewで入るバイナリだとダメっぽいんで、とりあえずDHを使わないcipher suite(<code>DEFAULT</code>でも大丈夫でした)を指定して回避できるようです。</p> <pre class="code" data-lang="" data-unlink>$ mysql --ssl-cipher=&#39;DEFUALT&#39; -u kamipo -h mysql-casual.rds.amazonaws.com</pre> <p>以上、入社エントリ以来ひさしぶりにブログ書いた😊</p> kamipo Treasure Dataに入社しました hatenablog://entry/8454420450090270236 2015-04-03T14:06:33+09:00 2020-08-26T12:34:10+09:00 近況などをブログに書いたことはなかったんですが、4月からTreasure Dataで働くことになりました。 3月に新しい仕事を探してたタイミングでちょうど声をかけてもらって、他に誘ってくれてるところもあっていろいろ考えたんですけど、今まで自分がやってたWeb屋さんとは結構ちがう専門的なプロダクトが面白そうだったこと、話してみてエンジニアリング上の解決したい課題についてすごく具体的にいろいろ話してくれたので、畑違いな気もするけどやれることは結構ありそうだなとイメージできたので入社することにしました。 あとは声をかけてくれるのが2週間遅かったら他のところに決めちゃってたので、お互いのタイミングが合… <p>近況などをブログに書いたことはなかったんですが、4月からTreasure Dataで働くことになりました。</p> <p>3月に新しい仕事を探してたタイミングでちょうど声をかけてもらって、他に誘ってくれてるところもあっていろいろ考えたんですけど、今まで自分がやってたWeb屋さんとは結構ちがう専門的なプロダクトが面白そうだったこと、話してみてエンジニアリング上の解決したい課題についてすごく具体的にいろいろ話してくれたので、畑違いな気もするけどやれることは結構ありそうだなとイメージできたので入社することにしました。</p> <p>あとは声をかけてくれるのが2週間遅かったら他のところに決めちゃってたので、お互いのタイミングが合ってたことで自分が想像していなかった選択肢が生まれたことにも面白さを感じて、まあこれも自分の中のひとつのチャレンジだと思って返事をしたという感じです。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/Hadoop">Hadoop</a>もFluentdもよく分からんしSlackも全部英語で、これはやばいところに来てしまったなという気持ちでいっぱいではあるけど、自分がこれまでやってたインフラだったり運用的な方面ではやれることあるっていうか無限にやることあってやばいみたいな感じなので、とりあえずめっちゃがんばれそうな気がしています。</p> <p>とりいそぎ今後ともよろしくお願いします!</p> <ul> <li><a href="http://www.amazon.co.jp/gp/registry/wishlist/3UCHGXACREMY4/">Amazon.co.jp: kamipo: Wishlist</a></li> </ul> <h4>SEE ALSO</h4> <ul> <li><a href="http://tagomoris.hatenablog.com/entry/2015/03/02/114321">&#x5C31;&#x8077;&#x3057;&#x307E;&#x3057;&#x305F; - &#x305F;&#x3054;&#x3082;&#x308A;&#x3059;&#x30E1;&#x30E2;</a><a href="https://b.hatena.ne.jp/entry/http://tagomoris.hatenablog.com/entry/2015/03/02/114321" class="http-bookmark"><img src="https://b.hatena.ne.jp/entry/image/http://tagomoris.hatenablog.com/entry/2015/03/02/114321" alt="" class="http-bookmark" /></a></li> <li><a href="http://myui.hateblo.jp/entry/joined_treasuredata">Treasure Data&#x306B;&#x5165;&#x793E;&#x3057;&#x307E;&#x3057;&#x305F; - myui&#39;s memo</a><a href="https://b.hatena.ne.jp/entry/http://myui.hateblo.jp/entry/joined_treasuredata" class="http-bookmark"><img src="https://b.hatena.ne.jp/entry/image/http://myui.hateblo.jp/entry/joined_treasuredata" alt="" class="http-bookmark" /></a></li> </ul> kamipo MySQL と寿司ビール問題 hatenablog://entry/8454420450089026830 2015-03-23T09:30:52+09:00 2016-02-26T18:37:32+09:00 MySQL と Unicode Collation Algorithm (UCA) - かみぽわーる に関連するトピックで、 MySQL には寿司ビール問題というのがある。 寿司ビール問題どっかで詳しくお話を聞くべきだよなぁ。。。— RKajiyama (@RKajiyama) March 18, 2015 これはどういう問題かというと、 MySQL の Unicode では binary collation にしてコードポイントで比較しないと🍣と🍺に限らず絵文字が同値判定されるという問題です。 あれ? MySQL の utf8mb4 charset って、4バイト文字同士を比較すると同じ文字… <p><a href="http://blog.kamipo.net/entry/2015/03/17/103457">MySQL &#x3068; Unicode Collation Algorithm (UCA) - &#x304B;&#x307F;&#x307D;&#x308F;&#x30FC;&#x308B;</a> に関連するトピックで、 <a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> には寿司ビール問題というのがある。</p> <p><blockquote class="twitter-tweet" data-lang="HASH(0xa873480)"><p lang="ja" dir="ltr">寿司ビール問題どっかで詳しくお話を聞くべきだよなぁ。。。</p>&mdash; RKajiyama (@RKajiyama) <a href="https://twitter.com/RKajiyama/status/578198944523534337">March 18, 2015</a></blockquote><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p> <p>これはどういう問題かというと、 <a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> の <a class="keyword" href="http://d.hatena.ne.jp/keyword/Unicode">Unicode</a> では binary collation にしてコードポイントで比較しないと🍣と🍺に限らず絵文字が同値判定されるという問題です。</p> <p><blockquote class="twitter-tweet" data-lang="HASH(0xa873480)"><p lang="ja" dir="ltr">あれ? <a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> の utf8mb4 charset って、4バイト文字同士を比較すると同じ文字扱いされる?<br>SELECT &#39;🍣&#39;=&#39;🍺&#39; → 1<br><a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>的には寿司とビールは同じ扱い。</p>&mdash; とみたまさひろ (@tmtms) <a href="https://twitter.com/tmtms/status/546925668424896512">December 22, 2014</a></blockquote><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script> <blockquote class="twitter-tweet" data-lang="HASH(0xa873480)"><p lang="ja" dir="ltr"><a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>で<br>select concat(欲しい物,&#39;おごってあげるよ&#39;) from 欲しい物リスト where 欲しい物=&#39;🍺&#39;<br>が<br>&#39;🍣おごってあげるよ&#39;<br>になる。<br>「🍣<a class="keyword" href="http://d.hatena.ne.jp/keyword/%C0%C8%BC%E5%C0%AD">脆弱性</a>」と名付けよう。</p>&mdash; とみたまさひろ (@tmtms) <a href="https://twitter.com/tmtms/status/546947655163596800">December 22, 2014</a></blockquote><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p> <p>なぜこんな挙動にしたのか、どういうケースのときにこの挙動だとうれしいのか全く理解が及ばないが、残念ながらこの挙動はドキュメントに明記されており、仕様である。</p> <p>以下に引用しますが、ざっくりいうと</p> <ul> <li>collating weight がある文字はそれを使う。ない場合は以下に従う。</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/BMP">BMP</a>文字で general collation (<strong>xxx_general_ci</strong>) の場合、コードポイントを使う。</li> <li><a class="keyword" href="http://d.hatena.ne.jp/keyword/BMP">BMP</a>文字で <a class="keyword" href="http://d.hatena.ne.jp/keyword/unicode">unicode</a> collation (<strong>xxx_<a class="keyword" href="http://d.hatena.ne.jp/keyword/unicode">unicode</a>_ci</strong>) の場合、なんかいい感じの計算式で導出する。</li> <li>SMP文字(絵文字とか)の場合、<strong>0xfffd REPLACEMENT CHARACTER</strong> と同じ weight になる。</li> </ul> <p>とのこと😨。</p> <blockquote><p>For all <a class="keyword" href="http://d.hatena.ne.jp/keyword/Unicode">Unicode</a> collations except the “binary” (<strong>xxx_bin</strong>) collations, <a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> performs a table lookup to find a character's collating weight. This weight can be displayed using the <a href="http://dev.mysql.com/doc/refman/5.6/en/string-functions.html#function_weight-string">WEIGHT_STRING()</a> function. (See <a href="http://dev.mysql.com/doc/refman/5.6/en/string-functions.html">Section 12.5, “String Functions”</a>.) If a character is not in the table (for example, because it is a “new” character), collating weight determination becomes more complex:</p> <ul> <li>For <a class="keyword" href="http://d.hatena.ne.jp/keyword/BMP">BMP</a> characters in general collations (<strong>xxx_general_ci</strong>), weight = code point.</li> <li>For <a class="keyword" href="http://d.hatena.ne.jp/keyword/BMP">BMP</a> characters in UCA collations (for example, <strong>xxx_<a class="keyword" href="http://d.hatena.ne.jp/keyword/unicode">unicode</a>_ci</strong> and language-specific collations), the following algorithm applies:<br/> (snip)</li> <li>For supplementary characters in general collations, the weight is the weight for <strong>0xfffd REPLACEMENT CHARACTER</strong>. For supplementary characters in UCA 4.0.0 collations, their collating weight is <strong>0xfffd</strong>. That is, to <a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a>, all supplementary characters are equal to each other, and greater than almost all <a class="keyword" href="http://d.hatena.ne.jp/keyword/BMP">BMP</a> characters.</li> </ul> <p><cite><a href="http://dev.mysql.com/doc/refman/5.6/en/charset-unicode-sets.html">http://dev.mysql.com/doc/refman/5.6/en/charset-unicode-sets.html</a></cite></p></blockquote> <p>なので、絵文字を検索で区別する必要がある場合は <strong>utf8mb4_bin</strong> にするしかないんですが、'ハハ' = 'パパ' 問題を気にしなければもうひとつ方法があることをドキュメント読んでて知りました。</p> <p><strong>xxx_<a class="keyword" href="http://d.hatena.ne.jp/keyword/unicode">unicode</a>_ci</strong> というのは UCA 4.0.0 というかなり古いと思われる仕様に対する実装で、より新しい UCA 5.2.0 を実装した <strong>xxx_<a class="keyword" href="http://d.hatena.ne.jp/keyword/unicode">unicode</a>_520_ci</strong> だとSMP文字にも weight を持っており、 weight がなくてもいい感じの計算式で導出するので <strong>0xfffd REPLACEMENT CHARACTER</strong> と同じ weight にはならないと書いてある。</p> <blockquote><ul> <li>For supplementary characters based on UCA versions later than 4.0.0 (for example, <strong><em>xxx_<a class="keyword" href="http://d.hatena.ne.jp/keyword/unicode">unicode</a>_520_ci</em></strong>), supplementary characters do not necessarily all have the same collation weight. Some have explicit weights from the UCA <em>allkeys.txt</em> file. Others have weights calculated from this algorithm:<br/> (snip)</li> </ul> <p><cite><a href="http://dev.mysql.com/doc/refman/5.6/en/charset-unicode-sets.html">http://dev.mysql.com/doc/refman/5.6/en/charset-unicode-sets.html</a></cite></p></blockquote> <p>実際試してみるとこんな感じ。まず区別できない例。</p> <pre class="code lang-mysql" data-lang="mysql" data-unlink>SET NAMES utf8mb4; <span class="synStatement">DROP</span> <span class="synStatement">TABLE</span> IF <span class="synStatement">EXISTS</span> wishlist_general; <span class="synStatement">CREATE</span> <span class="synStatement">TABLE</span> wishlist_general ( item <span class="synType">varchar(</span><span class="synConstant">1</span><span class="synType">)</span> ) COLLATE utf8mb4_general_ci; <span class="synStatement">DROP</span> <span class="synStatement">TABLE</span> IF <span class="synStatement">EXISTS</span> wishlist_unicode; <span class="synStatement">CREATE</span> <span class="synStatement">TABLE</span> wishlist_unicode ( item <span class="synType">varchar(</span><span class="synConstant">1</span><span class="synType">)</span> ) COLLATE utf8mb4_unicode_ci; <span class="synStatement">INSERT</span> <span class="synStatement">INTO</span> wishlist_general <span class="synStatement">VALUES</span> (<span class="synConstant">'🍣'</span>), (<span class="synConstant">'🍺'</span>); <span class="synStatement">INSERT</span> <span class="synStatement">INTO</span> wishlist_unicode <span class="synStatement">VALUES</span> (<span class="synConstant">'🍣'</span>), (<span class="synConstant">'🍺'</span>); <span class="synStatement">SELECT</span> item, <span class="synIdentifier">HEX(WEIGHT_STRING(item)</span>) <span class="synStatement">FROM</span> wishlist_general <span class="synStatement">WHERE</span> item = <span class="synConstant">'🍣'</span>; +------+--------------------------+ | item | <span class="synIdentifier">HEX(WEIGHT_STRING(item)</span>) | +------+--------------------------+ | 🍣 | FFFD | | 🍺 | FFFD | +------+--------------------------+ <span class="synStatement">SELECT</span> item, <span class="synIdentifier">HEX(WEIGHT_STRING(item)</span>) <span class="synStatement">FROM</span> wishlist_unicode <span class="synStatement">WHERE</span> item = <span class="synConstant">'🍣'</span>; +------+--------------------------+ | item | <span class="synIdentifier">HEX(WEIGHT_STRING(item)</span>) | +------+--------------------------+ | 🍣 | FFFD | | 🍺 | FFFD | +------+--------------------------+ </pre> <p>区別できる例。</p> <pre class="code lang-mysql" data-lang="mysql" data-unlink>SET NAMES utf8mb4; <span class="synStatement">DROP</span> <span class="synStatement">TABLE</span> IF <span class="synStatement">EXISTS</span> wishlist_unicode_520; <span class="synStatement">CREATE</span> <span class="synStatement">TABLE</span> wishlist_unicode_520 ( item <span class="synType">varchar(</span><span class="synConstant">1</span><span class="synType">)</span> ) COLLATE utf8mb4_unicode_520_ci; <span class="synStatement">DROP</span> <span class="synStatement">TABLE</span> IF <span class="synStatement">EXISTS</span> wishlist_bin; <span class="synStatement">CREATE</span> <span class="synStatement">TABLE</span> wishlist_bin ( item <span class="synType">varchar(</span><span class="synConstant">1</span><span class="synType">)</span> ) COLLATE utf8mb4_bin; <span class="synStatement">INSERT</span> <span class="synStatement">INTO</span> wishlist_unicode_520 <span class="synStatement">VALUES</span> (<span class="synConstant">'🍣'</span>), (<span class="synConstant">'🍺'</span>); <span class="synStatement">INSERT</span> <span class="synStatement">INTO</span> wishlist_bin <span class="synStatement">VALUES</span> (<span class="synConstant">'🍣'</span>), (<span class="synConstant">'🍺'</span>); <span class="synStatement">SELECT</span> item, <span class="synIdentifier">HEX(WEIGHT_STRING(item)</span>) <span class="synStatement">FROM</span> wishlist_unicode_520 <span class="synStatement">WHERE</span> item = <span class="synConstant">'🍣'</span>; +------+--------------------------+ | item | <span class="synIdentifier">HEX(WEIGHT_STRING(item)</span>) | +------+--------------------------+ | 🍣 | FBC3F363 | +------+--------------------------+ <span class="synStatement">SELECT</span> item, <span class="synIdentifier">HEX(WEIGHT_STRING(item)</span>) <span class="synStatement">FROM</span> wishlist_unicode_520; +------+--------------------------+ | item | <span class="synIdentifier">HEX(WEIGHT_STRING(item)</span>) | +------+--------------------------+ | 🍣 | FBC3F363 | | 🍺 | FBC3F37A | +------+--------------------------+ <span class="synStatement">SELECT</span> item, <span class="synIdentifier">HEX(WEIGHT_STRING(item)</span>) <span class="synStatement">FROM</span> wishlist_bin <span class="synStatement">WHERE</span> item = <span class="synConstant">'🍣'</span>; +------+--------------------------+ | item | <span class="synIdentifier">HEX(WEIGHT_STRING(item)</span>) | +------+--------------------------+ | 🍣 | 01F363 | +------+--------------------------+ <span class="synStatement">SELECT</span> item, <span class="synIdentifier">HEX(WEIGHT_STRING(item)</span>) <span class="synStatement">FROM</span> wishlist_bin; +------+--------------------------+ | item | <span class="synIdentifier">HEX(WEIGHT_STRING(item)</span>) | +------+--------------------------+ | 🍣 | 01F363 | | 🍺 | 01F37A | +------+--------------------------+ </pre> <p>とりあえず現状これが仕様だというのは分かったけど、どう考えてもSMP文字を <strong>0xfffd REPLACEMENT CHARACTER</strong> と同じ weight にするの現世においてデメリットしかないと思うんで、これがちゃんと区別されるようになるのを願ってやまないです。</p> kamipo MySQL と Unicode Collation Algorithm (UCA) hatenablog://entry/8454420450088357616 2015-03-17T10:34:57+09:00 2015-03-17T14:34:22+09:00 utf8_unicode_ci に対する日本の開発者の見解 - かみぽわーる で、日本語が分かる人には utf8_unicode_ci のヤバさを感じてもらえたと思うんですけど、この挙動はドキュメントによると UCA というアルゴリズムによるものらしい。 MySQL implements the xxx_unicode_ci collations according to the Unicode Collation Algorithm (UCA) described at http://www.unicode.org/reports/tr10/. The collation uses the … <p><a href="http://blog.kamipo.net/entry/2015/03/08/145045">utf8_unicode_ci &#x306B;&#x5BFE;&#x3059;&#x308B;&#x65E5;&#x672C;&#x306E;&#x958B;&#x767A;&#x8005;&#x306E;&#x898B;&#x89E3; - &#x304B;&#x307F;&#x307D;&#x308F;&#x30FC;&#x308B;</a> で、日本語が分かる人には utf8_<a class="keyword" href="http://d.hatena.ne.jp/keyword/unicode">unicode</a>_ci のヤバさを感じてもらえたと思うんですけど、この挙動はドキュメントによると UCA という<a class="keyword" href="http://d.hatena.ne.jp/keyword/%A5%A2%A5%EB%A5%B4%A5%EA%A5%BA%A5%E0">アルゴリズム</a>によるものらしい。</p> <blockquote><p><a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> implements the xxx_<a class="keyword" href="http://d.hatena.ne.jp/keyword/unicode">unicode</a>_ci collations according to the <a class="keyword" href="http://d.hatena.ne.jp/keyword/Unicode">Unicode</a> Collation Algorithm (UCA) described at <a href="http://www.unicode.org/reports/tr10/">http://www.unicode.org/reports/tr10/</a>. The collation uses the version-4.0.0 UCA weight keys: <a href="http://www.unicode.org/Public/UCA/4.0.0/allkeys-4.0.0.txt">http://www.unicode.org/Public/UCA/4.0.0/allkeys-4.0.0.txt</a>. Currently, the xxx_<a class="keyword" href="http://d.hatena.ne.jp/keyword/unicode">unicode</a>_ci collations have only partial support for the <a class="keyword" href="http://d.hatena.ne.jp/keyword/Unicode">Unicode</a> Collation Algorithm. Some characters are not supported yet. Also, combining marks are not fully supported. This affects primarily Vietnamese, Yoruba, and some smaller languages such as Navajo. <cite><a href="http://dev.mysql.com/doc/refman/5.6/en/charset-unicode-sets.html">http://dev.mysql.com/doc/refman/5.6/en/charset-unicode-sets.html</a></cite></p></blockquote> <p>これもしかして、 <a class="keyword" href="http://d.hatena.ne.jp/keyword/Unicode">Unicode</a> って日本語のこと分かってない人たちによって作られてて日本語の検索とかなんも考慮されてないんとちゃうんかと思って調べてみたら、 そんなことはまったくなくて <a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> が UCA を部分的にしか実装していないということだった。</p> <p><a href="http://www.unicode.org/reports/tr10/">UTS #10: Unicode Collation Algorithm</a> によると、 UCA では Multi-Level Comparison といって<a class="keyword" href="http://d.hatena.ne.jp/keyword/%CA%A3%BF%F4">複数</a>レベルに分けて文字列の比較を行い、各レベルの比較で文字列が一致した場合(tie-breaking level)、次のレベルで比較を行うを繰り返して順序を決定する。L1は大文字小文字アクセント無視(Base Charaters)、L2はアクセントを無視しない(Accents)、L3は大文字小文字を無視しない(Case/Variants)、のような感じである。</p> <p><a class="keyword" href="http://d.hatena.ne.jp/keyword/MySQL">MySQL</a> が UCA を部分的にしか実装していないとはどういうことかというと、 <a href="http://www.unicode.org/Public/UCA/5.2.0/allkeys.txt">allkeys.txt</a> でいうところの Primary weight range (L1比較のためのweight) しか実装していないので、アクセントを区別しないと常用に適さない言語(日本語とか)殺しな挙動になっているということだった。</p> <p><a href="https://github.com/mysql/mysql-server/blob/mysql-5.7.6/strings/ctype-uca.c#L17-L31">mysql-5.7.6/strings/ctype-uca.c#L17-L31</a> からコメントを引用する。</p> <pre class="code" data-lang="" data-unlink>/* UCA (Unicode Collation Algorithm) support. Written by Alexander Barkov &lt;bar@mysql.com&gt; Currently supports only subset of the full UCA: - Only Primary level key comparison - Basic Latin letters contraction is implemented - Variable weighting is done for Non-ignorable option Features that are not implemented yet: - No Normalization From D is done + No decomposition is done + No Thai/Lao orderding is done - No combining marks processing is done */</pre> <p>アクセントを区別しないと常用に適さない言語が日本語以外にどれぐらいあるのか言語に詳しくないので分からないけど、これを理由に以下のコミットをrevertできたりしないですかね。</p> <p><blockquote class="twitter-tweet" lang="en"><p>utf8_<a class="keyword" href="http://d.hatena.ne.jp/keyword/unicode">unicode</a>_ciにしたコミット(<a href="https://t.co/XqfVL5LCfn">https://t.co/XqfVL5LCfn</a>)をrevertするブランチ手元にはあるけど僕の英語力では説得できる気がしないから助けてくれそうな仲間が増えるまでPR出せそうにない <a href="http://t.co/UkGhJUOCw1">pic.twitter.com/UkGhJUOCw1</a></p>&mdash; Ryuta Kamizono (@kamipo) <a href="https://twitter.com/kamipo/status/562436496267829248">February 3, 2015</a></blockquote><script async src="//platform.twitter.com/widgets.js" charset="utf-8"></script></p> <h4>参考</h4> <ul> <li><a href="http://dev.mysql.com/doc/refman/5.6/en/charset-unicode-sets.html">MySQL :: MySQL 5.6 Reference Manual :: 10.1.14.1 Unicode Character Sets</a></li> <li><a href="https://bugs.mysql.com/bug.php?id=16526">MySQL Bugs: #16526: utf8_unicode_ci can&#39;t distinguish some Japanese characters</a></li> <li><a href="http://tmtms.hatenablog.com/entry/20110416/mysql_unicode_collation">MySQL 5.5 &#x306E; unicode collation &#x3067;&#x540C;&#x4E00;&#x8996;&#x3055;&#x308C;&#x308B;&#x6587;&#x5B57; - @tmtms &#x306E;&#x30E1;&#x30E2;</a></li> <li><a href="http://www.nminoru.jp/~nminoru/programming/collation.html">&#x6587;&#x5B57;&#x5217;&#x306E;&#x7167;&#x5408;&#x9806;&#x5E8F;(Collation)</a></li> </ul> kamipo