Cloudflare上でトランザクションが成功している(ように見える)のに、DBに反映されていない現象に遭遇した

Cloudflareで動作する、個人開発プロジェクトで、次のような問題に遭遇しました。

手元マシンの開発環境でnpm run devで実行し、ブラウザでアクセスしているときは、バックエンド処理でDBトランザクションが行われて成功し、(当然)DBも更新されるのに、いざCloudflare同じ処理を動かすと、DBトランザクションが成功している(ように見える)のに、DBが更新されていない。

まず、Cloudflareの実行時間制限にかかっているのか、と疑いましたが、エラーも全く起きていないので、これが原因ではなさそうでした。

アプリケーション上で他の箇所でもトランザクションを使っているところがあり、それらはCloudflareとローカル開発環境で動作の違いは無いのに、ある特定のリクエストだけ現象が起きていました。

ざっくりと、以下のような処理です。

const deleteById = async (db: TiDBServerlessDatabase<typeof schema>, form: FormData) => {
    const id = form.get('id')
    try {
        db.transaction(async (tx) => {
            await tx.delete(TableA).where(eq(TableA.id, id))
            await tx.delete(TableB).where(eq(TableB.id, id))
        })
    } catch (e) {
        return { status: 'fail', msg: e }
    }
    return { status: 'success', msg: '削除しました' }
}

Remixのactionとして動作しており、returnが行われると、HTTP応答がブラウザに帰ります。

上記コードの問題は何でしょうか???? 正解は↓

const deleteById = async (db: TiDBServerlessDatabase<typeof schema>, form: FormData) => {
    const id = form.get('id')
    try {
        await db.transaction(async (tx) => {
            await tx.delete(TableA).where(eq(TableA.id, id))
            await tx.delete(TableB).where(eq(TableB.id, id))
        })
    } catch (e) {
        return { status: 'fail', msg: e }
    }
    return { status: 'success', msg: '削除しました' }
}

db.transactionの前にawaitが抜けていました。それで、実際の動作順としてはreturnでHTTP応答が行われた後にトランザクションが動作していたようです。Cloudflareだと、HTTPリクエストが完了すると、直ちにworkerのインスタンスが破棄されるようで、トランザクションが終わるところまで処理が行われていなかったと思われます。手元のNode.jsだと当然そういうこともないので、awaitが抜けていてもトランザクションが完了し、DBが更新されていたわけです。

MX Keys Miniを買った

MX Keys Miniを購入した。

ここ2年半くらい、REALFORCE R3Sを仕事と個人で利用してきた。キーボードには大きな不満は無い。細かいところでは、黒モデルなので、白モデルより多少ねっとり(?)した感触があり、白にしておけば良かったかも?と感じたことと、白よりは汚れが気になる、というくらい。30gの荷重というのも、軽くて良い。

ところが、別のところに多少問題が起きていた。仕事用PCと個人用PCに同じキーボード(とマウス)を接続するために、安価なKVM(こういうやつ)を使っているのだけど、これが少し不安定で、たまにUSBの接続がしばらく切れたりする。作業中に発生すると、かなり煩わしい。1台目で症状が出て、別のメーカーの2台目を利用しているが、こちらも最近不安定になっている。

そういったこともあり、そろそろUSBケーブルでの接続をやめて、無線化しようかと思い始めた。

すでにタブレット用に所有していたK380sを試しに仕事で使ってみたところ、キータッチ自体は、さすがにREALFORCEと比べると快適とは言い難いけど、無理ではない感じで、それより70%キーボードのサイズ感と、無線というところが、大変気持ちよく感じた。

それで、少し奮発して、上記MX Keys Miniを購入した。REALFORCE RC1という選択もあったんだけど、ちょっと高いし、キーの最下段が少しずれている、というのも気になる(K380sも最下段は少しずれている)。どうせ無線接続ならNキーロールオーバーというのもあまり役立たないと思われるし。

それに合わせて、マウスも無線化した。M750でこちらもマルチポイント接続で切り替えというのも検討したけど、キーボードとマウスを毎回切り替えるのも、それはそれで手間のように感じたので、こちらは安いM185にした。マウスは小さいので机の上に複数個あってもそれほど気にならない(と思う)ので…

歴代キーボード

写真上から、REALFORCE R3S, MX Keys Mini, K380s。K380sはMX Keysと比べてもキーピッチが少し狭いので、全体的に横幅も小さくなっている。MX Keys MiniはREALFORCEと比べてわかるように、標準的なキーピッチ。

MX Keys Miniのキータッチは、悪くはないけど、個人的好みとしてはREALFORCEには流石に及ばない感じがした。変則サイズのキーもないし、ミスタッチしやすいということも全く無いので、特段の問題は無さそう。やはりこのサイズ感と線が出ていないところが良い。

Remixでは副作用のためのモジュールインポートは避けた方が良い

引き続きRemixで開発していたところ、クラアント側で動作させたいコードで、副作用モジュールインポートを行っていると問題が起きることがわかりました:

import 'hoge'

上記のようなimportです。このimportを行う側のコードでは、hogeの中身を名前で参照していませんので、このimportは、hogeのロード時1度限りのコード実行を目的としたものです。

今回このhogeの中身では、グローバルオブジェクトに新しいプロパティを付け加える処理を行っています。

$ npm run dev

で開発しているときには、上記のimportを行うコードは正しく実行されていたのですが、

$ npm run build
$ npm run start

にて、ビルドした成果物を動かすと、hogeが全く動作していませんでした(hogeで付け加えられるべきプロパティが存在しなかったため、エラーが起きた)。

散々悩んだ挙げ句、Module Constraintsで説明されている通り、Remixで上記のような副作用を目的としたモジュールのimportは行わない方が良さそうです。

今回の目的では、hogeの実行はimport時ではなく、ページ初期化後でも構わなかったため、hogeの中身をexport functionとし、副作用モジュールインポートを、関数のインポートに置き換えました。そして、当該functionをuseEffectの中から呼び出すようにました。この方法は、Lazy Initializationで紹介されているものです。

動作確認したバージョン

  • @remix-run/cloudflare: 2.10.3
  • remix-utils: 7.6.0
  • react: 18.3.1

RemixでSSRをバイパスするにはremix-utilsを使おう

Remixleafletを使用する開発で、SSRフレームワーク定番(参考1, 参考2)の"window is not defined"がサーバ側の端末で表示される現象に遭遇しました。

要するに、ブラウザでのみ動かしたいコードがサーバ側のSSRで動いてしまっているわけです。

Next.jsだとdynamicで囲って対処していましたが、Remixでどうやってやるのか調べました。

Remix Viteで一部をクライアントサイドのみにするでは、ReactのlazyとSuspenseを組み合わせて行けるとのことでしたが、私が試した限りではうまくいきませんでした(やり方が悪かっただけかもしれません)。

結局、remix-utilsClientOnlyを使って解決しました。

動作確認したバージョン

  • @remix-run/cloudflare: 2.10.2
  • remix-utils: 7.6.0
  • react: 18.3.1

フロントエンド界隈は開発が活発なので、この記事の内容もあっという間に陳腐化するかもしれませんが、備忘録として残しておきます。

Speed Wi-Fi HOME 5G L13で楽天モバイルを使う

楽天モバイルを契約してからしばらくの間、スマートフォンのテザリングでデータ通信を利用していました。 スマートフォン本体でスピードテストを行うと、300Mbps以上出ることがあるものの、 テザリングだと、おそらくスマートフォン側の仕様上そこまで性能が出なくて、せいぜい50-60Mbpsが良いところでした。

これでは回線性能を活かせないので、ホームルータをメルカリで購入しました。Speed Wi-Fi HOME 5G L13です。8000円台で購入できました。

最初から入っているinternetという名前のプロファイルは削除/編集できなかったので、rakutenという名前で楽天モバイルのAPNを入力したプロファイルを作成しただけで使用できるようになりました。

PCから802.11ac接続してスピードテストを行った結果画像です:

Speedtest

これで回線速度を活かせそうです。これだけあれば、自分の利用方法での範囲内では、光回線は不要そうです。レイテンシや上り速度の面では、光接続のほうが断然良いと思いますが。

LAN1ポートでPCに接続すると、なぜかオートネゴシエーションに失敗して1Gbpsではなく100Mbpsに落ちてしまう事があるのですが、これは必要に応じて調べたいと思います。とりあえず無線接続でも十分早いので。

MySQLもしくはPostgreSQLで, 1文で2行の特定のカラムの値をスワップする方法

DBに入った2行の、特定のカラムの値をスワップする方法。ちょっと日本語で検索しても良いのが出てこなかったけど、英語で良いのが出てきた(下記はMySQL限定):

Mysql: Swap data for different rows

UPDATE tbl a INNER JOIN tbl b ON a.id <> b.id
SET a.col = b.col
WHERE a.id IN (2, 3)
  AND b.id IN (2, 3)

上記は、テーブルtblのカラムcolの中身を、id=2,3のレコードでスワップする。

これ最初に思いついた人、頭良い。そもそもupdate文でこういうjoinが書けるのを正確に知らなかった。SQL奥深し。updateで使える構文を一度しっかり調べたほうが良さそう。

2023/5/23追記:PostgreSQLでは上記の方法では動かない。次のような感じでいける(参考資料:PostgreSQL, Swap data of certain column in two rows)

UPDATE tbl a set col = b.col
FROM tbl b
WHERE a.id IN (2, 3)
  AND b.id IN (2, 3)
  AND a.id <> b.id

ただ、実際に動作させてみると、colにUNIQUE制約がかかっている とエラーになってしまう。どうやらPostgreSQLの場合、内部的には同時に交換というよりは逐次で更新されているっぽい。さらにちなみに、上記のset colのところをset a.colとすると構文エラーになってしまうので注意。

楽天モバイルを再契約した

自宅ではExcite MEC光をしばらく利用してきましたが、VDSLでとても遅いのが気になっていました。 下りは24-25Mbps、上りは10Mbps程度が限界でした。これで月々3,500円と10%消費税で3850円はちょっと高いと感じていました。

以前契約していた楽天モバイル は、無料期間が終わったときに解約していましたが、再度契約してみました。 縛りがなくてすぐに解約できますし、通信量制限も取り払われたようなので。

Rakuten Hand 5Gというのが1円で購入できました。こちらをセットアップしてWiFiテザリングでスピードテストをしてみたところ、ばらつきはありますが、たとえば、Down 44Mbps / Up 30Mbpsといった値が出ました(19時台に測定)。自宅は残念ながら5GエリアではないのでLTE接続です。

確かにジッタやレイテンシの点では光回線の方が良いかと思いますが、帯域としてはこちらのほうが上のようですので、 早速Excite MEC光は解約申し込みしてしまいました。

机の上を綺麗にするためにKVMを購入した

これまで机の上に会社用PCのマウスとキーボード、個人PC用のマウスとキーボードと、2セット置いていたのですが、急に邪魔に感じるようになってきたので、KVMを購入しました。

安い中華製のものを購入しましたが、今のところ問題なく使えているようです(Realforce R3Sとエレコムの安い有線マウスを接続しています)。これで机上がスッキリしました。

毎日、朝と仕事後にキーボードとマウスの配置換えをしていましたが、これが(当然ですが)ボタン一発で切り替えられるのも嬉しいです。

高いものではないので、もっと早く購入していれば良かったです。

GitHub CodespacesでのNext.jsアプリ開発で調査した資料

GitHub Codespaces便利ですよね。無料枠だと一ヶ月あたり120時間使用できますが、本業とは別にちょっと開発するのであれば、これくらいの時間があれば十分に感じます。

CodespacesでNext.js/NextAuth.jsでOAuthを使用したWebアプリケーションを作成して、Codespacesで自動的に割り当てられるHTTPSのURLをコールバックに指定できるので、ログインが必要なアプリも開発できます。

ただ、色々やっているうちに、502 Bad Gatewayエラーが出るようになってしまいました。ちょっと検索してみたら、Port Forwarding returns 502 Bad Gatewayに従って、一旦Codespacesのインスタンスを削除して、再度作成したら解消しました… と思ったら、それでも502が続くようになってしまいました… 2023/3/29現在、解決方法は見つかっていません。

RDBはprismaを使用してアクセスしているのですが、検索条件として同一テーブルの異なるカラム同士間の条件で行を抽出したいと思った時、その方法がわかりませんでした。こちらも調べてみたら、Compare columns in the same tableを見つけました。generator clientの中にpreviewFeatures = [“fieldReference”]を追加してprisma generateすると、prisma.table.fields.columnといた感じでカラムを条件部で参照できるようになりました(tableがテーブルに対応するスキーマ名で、columnが参照したいカラム名)。ドキュメントではversion 4.3.0から使えるようになっていると書かれていますが、手元では4.11では動かなかったように思います。4.12に上げたらfieldsが出てきました。

JBL PebblesをEdifier P12にアップグレードした

2017年にJBL Pebblesを購入してから、大きな不満なく使用してきましたが、もう少し良い音を楽しみたいな、と最近欲が出てきました。

また、音質とは違いますが細かい不満として、自宅では(同時に使うことはほとんど無いものの)複数台のPCを使用しており、起動するPCにPebblesのUSBケーブルを接続し直す、ということを毎回のように行っていました。

そんなわけで、もう少し音声入力方法も改善したいというのもありました。

Amazonでとにかくいろいろ検索してみました。かなり悩みました。

最初は価格コムでも高評価のEris E3.5にしようかと思ったのですが、最安で購入できそうなヨドバシだと納期が2ヶ月くらいかかるので、残念ながら候補から外しました。

次は、評価も高かったYAMAHAのNS-BP200BPに傾いたのですが、奥行きサイズ287mmが引っかかりました。120cm x 70cmの事務机の上に設置するのに問題がありました。物理的には配置可能ですが、右側にモニターアームがあり、干渉してしまうことが明らかでした。

それで、最終的にEdifier P12を購入しました。パッシブスピーカーなので、別途アンプが必要です。P12のレビュー欄でオススメされていたXY-AP15Hも購入しました。ところが、中国発送らしく、発送の連絡は来たものの、到着まで時間がかかるので、先にスピーカーだけ納入されてしまいました。どうしても音を鳴らしてみたいという欲求がありましたので、年内に納入可能だったBluetoothステレオボードも購入してしまいました。

上記2つのアンプは電源が必要なので、DC12V3A電源アダプタも購入しました。外径内径はこの商品で適合するはずです(少なくとも後者のアンプは使用確認済み)。それほど爆音で鳴らすわけでもないので、とりあえず36Wで。

到着したP12をJBL Pebblesの横に置いてみました。

到着したP12をJBL Pebblesの横に置いてみました。

中華Bluetoothアンプが奥に写っていますが、拡大したものがこちらです。

Bluetoothアンプ拡大

このアンプ、ボリュームつまみを左まで回し切ると電源が切れるようになっており、細かい配慮が気に入りました。若干芯線の露出部が長いのが怖いですが、それほどいじるわけではないので、多分ショートすることはないと見込んでカットしていません。

早速スピーカーを鳴らしてみました(まだアンプの関係でBluetooth接続のみ)。

定量的に評価できるような機材がないので、主観でしかないのですが、5年使用したPebblesより低音部が鳴っています(離れた場所で低音が聴こえるので違いがわかる)。ただ、Pebblesもバランスは高いと確認できました。聴き比べると、若干Pebblesは高音が刺さる感じ。Pebblesはボーカルに最適化されているのか、ボーカルが目立つ気がします。P12は長時間の視聴には良いのではないかと思います。クラシックを聴くなら、低音が出ている分、P12の方が圧倒的に良いように感じます。ポップスであれば、ひょっとすると好みの問題かも知れません(所詮YouTubeの音源だとそれほど違いがわからない??)。

流石に電力的にもサイズ的にも大きいので、Pebblesよりも迫力(音圧)があるような気がするようなしないような。むしろBluetoothで再生できるのがとても便利です(そこ??)。PC起動しなくてもiPadや携帯から飛ばせますし。

また、音質とは直接は関係無いですが、今回使用したBluetoothアンプは、無音期間後に音が出る場合、最初の一瞬だけ音が出ないようです。どういう仕組みでこうなっているのかわかりませんが、省電力化のために無音時にスピーカ出力を抑制しているのでしょうか。

思いの外、机上に2つスピーカーを設置しても問題なかったので、しばらく両方のスピーカーを気分で併用することにしました。もう一つのアンプが到着したら、有線接続での再生も試してみたいと思います。

2023/1/19追記: XY-AP15H到着したので使ってみました。正直なところ、Bluetoothで接続する限り、音質の違いは分かりませんでした。こちらはAUX inで3.5mmジャックが使えるのですが、どうやら本体に刺しただけで他の入力(Bluetooth, USB)が無効になってしまうようで、使い勝手は微妙な感じでした。USBについても、挿抜をしないとPCから認識されないことがあるようでした。というわけで、結局一番手軽なBluetoothで使うことになりそうなので、Bluetoothステレオボードのままでも良かったかもしれません….

でも、スピーカがエージングされたのか、自分の耳が最適化されたのか分かりませんが、やはりJBL Pebblesより音質面では満足しています。慣れた後にPebblesを聴くと安っぽく(?)感じます。セット価格でもPebblesの倍近くかかっていますので、これくらい効用がないと意味がありませんが…