私は空き時間を使って、個人開発のプロダクト ShenDesk(オンラインカスタマーサポート&マーケティングシステム) を開発してきました。
少しずつ、数年かけて作り続け、最初は「試しに使ってみる」程度のユーザーしかいませんでしたが、今ではクラウド版・オンプレミス版の両方で、安定して利用してくれるユーザーが着実に増えています。
その過程で、「個人開発プロダクトをどう作り、どう運営していくか」について、かなりの経験を積むことができました。
正直に言うと、これまで何度もリリース後にこう思ってきました。
「このバージョンはもう、絶対に安定してるだろ 😎」
……ところが、しばらくすると必ずこうなる。
「え? そんな変な不具合、まだ出るの? 😗」
数年前までは、しょっちゅう「え?」が出ていました。
今年に入ってからは、たまに「え?」。
そして今。
ようやく本当に「老犬のように安定した」と言っていい状態になったと感じています。
というのも、ここ最近、複数の顧客環境を最新バージョンへアップグレードしましたが、すべて問題なく、静かに、安定して動いているからです。
自分自身の本番環境も同じです。
以前は、ユーザーから
「この環境でこんな問題が出た」
「この使い方だと挙動がおかしい」
といったフィードバックを頻繁にもらっていました。
しかし今では、利用者数も増え、オンライン訪問者の総数も確実に増えているにもかかわらず、
ほとんど誰からも不具合の連絡が来なくなりました…… 😒
このカスタマーサポートシステムは、私が空き時間に開発を始めてから、気づけば 5年 が経ちました。
この記事では、その5年間の中で何度も私を唖然とさせた――
「え? そんな変な不具合、まだあるの?」
という類の出来事を記録していきます。
え? そんな変な不具合あるの?
たった一つの絵文字が履歴を消し飛ばす:UTF-8 と DB 文字コード不一致問題
「昨日のチャット内容が、今日見たら全部消えてるんですが?!」
深夜までログを追いかけた結果、判明したのは衝撃的な事実でした。
データベースは 正常に 0 件を返している。
タイムアウトでもなく、権限エラーでもない。
まるで、そのチャット自体が最初から存在しなかったかのように。
さらに過去ログを辿ると、
そのオペレーターが送った最後のメッセージはこれでした。
「はい、少々お待ちください🥲」
この 小さな 🥲 一文字 が、
そのチャット履歴全体を破壊していた のです。
症状:INSERT は成功したように見えるが、実際には SQL が失敗している。
テーブル作成時、私は安易に「UTF-8」を指定していました。
しかし、MySQL における utf8 と utf8mb4 が全く別物だという事実を見落としていました。
CREATE TABLE chat_message (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
visitor_id VARCHAR(50),
content TEXT CHARACTER SET utf8 COLLATE utf8_general_ci,
created_at DATETIME
);
訪問者のメッセージに 4バイト文字(emoji や特殊記号) が含まれると、
INSERT は即座に失敗します。
INSERT INTO chat_message (visitor_id, content, created_at)
VALUES ('A123', 'はい、少々お待ちください🥲', NOW());
-- Error: Incorrect string value: '\xF0\x9F\xA5\xB2' for column 'content'
アプリケーション層の「偽の成功」:ドライバが例外を握り潰していた
使用していた .NET MySQL Connector では、
デフォルト設定で IgnorePrepare = true になっており、
さらにコード側で 詳細な SQL 例外を捕捉していなかった ため、
結果として
「INSERT 失敗 → でも成功扱い」
という最悪の状態が発生していました。
try
{
await db.ExecuteAsync(
"INSERT INTO chat_message (visitor_id, content, created_at) VALUES (@v, @c, @t)",
new { v = visitorId, c = content, t = DateTime.UtcNow }
);
logger.Info("メッセージ保存成功:" + content);
}
catch (Exception ex)
{
// SQL エラーコードを出力せず、ex.Message のみ記録 → 上位で無視される
logger.Warn("メッセージ保存例外:" + ex.Message);
}
そのため「保存できた」と信じていたメッセージは、
翌日オペレーターが確認すると 空っぽ だったわけです。
根本原因:MySQL の utf8 は「3バイト UTF-8」
MySQL の utf8 は、
1〜3バイト(BMP プレーン)までしか対応していません。
一方、emoji は U+1F600 以降のコードポイントを使用し、
4バイト UTF-8 が必要です。
🥲 = U+1F972
UTF-8 バイト列 = F0 9F A5 B2
このため、該当文字を含む INSERT はすべて失敗していました。
解決策:全面的に utf8mb4 へ移行+互換対応
Step 1:DB とテーブルの文字コード変更
ALTER DATABASE mychat CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
ALTER TABLE chat_message CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
Step 2:接続文字列で明示的に指定
var connStr = "Server=localhost;Database=mychat;Uid=root;Pwd=xxx;CharSet=utf8mb4;";
Step 3:emoji を含むデータで実動確認
var testMessage = "ShenDeskへようこそ🥳🔥";
await db.ExecuteAsync(
"INSERT INTO chat_message (visitor_id, content, created_at) VALUES (@v, @c, @t)",
new { v = "T001", c = testMessage, t = DateTime.UtcNow }
);
var result = await db.QuerySingleAsync<string>(
"SELECT content FROM chat_message WHERE visitor_id = @v ORDER BY id DESC LIMIT 1",
new { v = "T001" }
);
Console.WriteLine(result); // 出力: ShenDeskへようこそ🥳🔥
さらに深い落とし穴:utf8mb4 とインデックス長の衝突
utf8mb4 に切り替えた直後、
複合インデックスが突然作成できなくなりました。
ALTER TABLE chat_message ADD INDEX idx_v_c(visitor_id, content);
-- Error: Specified key was too long; max key length is 767 bytes
原因は単純です。
utf8mb4 は 1 文字最大 4 バイト。
VARCHAR(255) をそのままインデックスにすると、
InnoDB の制限を簡単に超えてしまいます。
対策:プレフィックスインデックス、もしくは全文検索
ALTER TABLE chat_message ADD INDEX idx_v_c(visitor_id, content(100));
まとめ:この件から得た教訓
- utf8 = UTF-8 だと思うな。 MySQL の utf8 は欠陥仕様。
-
ログと例外は SQL エラーコードまで正確に記録せよ。
さもないと「データ消失」に永遠に気づけない。 -
テストデータには必ず emoji を含めること。
本番環境では、無数の 🐱🐶 があなたのデータを破壊しに来る。
最終的に、私は以下のテストを追加しました。
it("should store emoji without error", async () => {
const message = "emoji テスト 🐱🐶🔥";
const res = await api.sendMessage({ visitorId: "U999", content: message });
const saved = await api.getLastMessage("U999");
expect(saved.content).toBe(message);
});
このテストは 毎回のデプロイ時に必ず実行 されます。
もう二度と、たった一つの 🥲 に履歴を壊されないために。
👋 最後に
現在も ShenDesk は進化を続けています。
もしあなたがライブチャットシステムを開発・導入した経験があるなら、
リアルタイム更新、負荷分散、柔軟なデプロイをどのように実現したか、ぜひ教えてください。
一緒に語りましょう。
🚀 ぜひお試しください
🌐 公式サイト:https://shendesk.com
📘 ドキュメント:https://docs.shendesk.com
オンラインでも、自分のサーバーでも、無料で体験できます。
セルフホストやリアルタイム通信、カスタマーエクスペリエンス改善に関心のある開発者からのフィードバックをお待ちしています。


