PR

個人開発のWebサービスで「困ったこと」3選

スキルアップ

前回は、音楽のリリースを発売順にたどれるWebサービス 「Release Order」 をリリースしたことを投稿しました

連載の第2回は、作るなかで「困ったこと」の話です。ローカルでは問題なく動いていたのに、本番で使い始めてから気づいたことがたくさんありました。その中から、特に「えっ、そうだったの?」となった3つを紹介します。

困ったこと①:配信リンクのAPIが終わっていた(しかも、最初から使い方を間違えていた)

Release Orderでは、各作品にSpotifyやApple Musicへのリンクを出しています。このリンクは、「Odesli(song.link)」という、曲のURLを渡すと各配信サービスのリンクをまとめて返してくれるAPIで取得する設計でした。

症状

リリースしてみると、どの作品も配信サービスの「検索結果ページ」へのリンクになっていました。APIで見つからないときの代わりに用意していた仕組みで、クリックすれば検索結果は出るので、しばらく気づきませんでした。

原因(1):APIそのものが終了していた

ログを見ると、APIから 401 Unauthorized が返ってきていました。調べてみると、Odesliの公開APIは 2026年7月31日で終了していて、新しいAPIキーの発行も止まっていました。

継続して使いたい開発者向けに、許可リストへの申請フォームが用意されていたので、用途や想定の利用量を書いて申請したところ、2日後に許可してもらえました。キーには「1分5回・1時間300回まで」という上限があるので、サーバー全体で回数を数えて、超えないようにしています。

原因(2):そもそも使い方が間違っていた

これで直る……と思ったら、キーを設定しても検索リンクのままでした…😭

もう一度仕様を調べ直して分かったのが、OdesliはISRC(曲ごとの国際コード)では検索できないということ。受け付けるのは「SpotifyやApple MusicなどのURL」だけでした。私のコードは最初からISRCを送っていたので、APIが終了する前から、ずっと失敗していたんです。

どう直したか

作品の情報を取得しているMusicBrainz(オープンな音楽データベース)を見直すと、配信版の作品にはSpotifyやApple MusicのURLが登録されていることが多いと分かりました。そこで、

  1. MusicBrainzから作品の配信URL(Spotify優先)を取得して保存する
  2. そのURLをOdesliに渡して、他のサービスのリンクをまとめて取る
  3. URLがない作品(CDだけの昔の作品など)は、これまでどおり検索リンク

という流れに変えたところ、ちゃんと作品のページへの直接のリンクが取れるようになりました。

学んだこと:「動いているように見える」と「正しく動いている」は別物。失敗したときの代わりの処理(フォールバック)が優秀だと、失敗そのものに気づけません。

困ったこと②:本番でだけ、時々500エラーが出る

症状

アーティストや作品のページを開くと、たまに「Server Error (500)」になる。しかも、同じページでも開き直すと普通に表示されます。ローカルでは何度試しても再現しませんでした。

手がかりになったのは、エラーになるときは必ず5秒ほど待たされてから500になることでした。

原因:バックグラウンド処理がDBの接続を閉じていなかった

Release Orderは「月額0円」で動かすために、重い処理(MusicBrainzからの取り込みや配信リンクの取得)を、専用のサーバーではなくWebサーバーの中のバックグラウンドスレッドで動かしています。

ここに落とし穴がありました。Djangoは、リクエストの処理が終わったときには自動でDBの接続を閉じてくれますが、自分で起動したスレッドの接続は閉じてくれません。

その結果、バックグラウンド処理が動くたびに接続が開いたまま残り、DB(Supabase)側の同時につなげる数の上限を使い切っていました。新しいリクエストは空きが出るのを待ち、約5秒後にあきらめて500になる、という流れです。

さらに悪いことに、配信リンクの保存も失敗していました(日本語の曲名を入れた検索URLが、保存できる長さの200文字を超えていた)。そのため、ページを開くたびに取得をやり直して、接続がどんどん増えていくという悪循環でした。

どう直したか

バックグラウンド処理が終わったら、必ず接続を閉じるようにしました。

def _run_safely(func, args, kwargs):
    try:
        func(*args, **kwargs)
    except Exception:
        logger.exception("バックグラウンド処理が失敗しました")
    finally:
        # リクエスト外のスレッドで開いた接続は、自分で閉じる
        connections.close_all()

あわせてURLを保存できる長さを1000文字に広げたところ、500エラーは出なくなりました(修正後に156件アクセスしてエラー0件)。

学んだこと:フレームワークが後片付けしてくれるのは、フレームワークの流れの中だけ。自分でスレッドを作ったら、後片付けも自分の責任です。

困ったこと③:シングルから曲が消える、作品が他のアーティストに奪われる

症状

「曲一覧(発売順)」を作っているときに、シングルの表題曲が、収録曲から消えている作品があることに気づきました。よりによって、いちばん大事な曲がです。

調べてみると、1つや2つではなく、曲が欠けている作品があちこちにありました。

原因:「同じ録音」を作品をまたいで1つしか持てなかった

MusicBrainzでは、曲の「録音」ごとにIDが付いています。シングルの表題曲が、のちのアルバムにもそのまま収録されると、両方に同じ録音IDが付きます。

コードは曲を「録音ID」だけで探して保存していたので、アルバムを取り込んだ瞬間に、シングルの曲がアルバムに「引っ越し」していました。

# 修正前:録音IDだけで探す → 他の作品の曲を奪ってしまう
Track.objects.update_or_create(mbid=recording_id, defaults={"release": release, ...})

# 修正後:作品と録音IDの組み合わせで探す
Track.objects.update_or_create(release=release, mbid=recording_id, defaults={...})

もう一つの「奪い合い」:連名の作品

同じことが作品にも起きていました。たとえばプリパラのアルバムは、声優さんが6人、32人と連名でクレジットされています。ところが私のデータの持ち方では、作品はアーティストを1人しか持てません。

そのため、別の声優さんを取り込むたびに作品の持ち主が書き換わり、ある声優さんのページでは作品が3件から1件に減っていました。「新譜を反映するために週に1回取り直す」処理を入れたことで、作品が声優さんの間を行き来するようになり、問題が表に出てきました。

どう直したか

  • 作品とアーティストを多対多で持てるようにして、連名の作品をクレジットされた全員のページに表示する
  • 曲が欠けている作品は、毎日の定期処理で少しずつ取り直す

今では、その声優さんのページにも作品がぜんぶ戻っています。

学んだこと:「そのデータは本当に1つに決まるか?」を、最初に考えること。音楽のデータは、同じ曲が複数の作品に入ったり、複数のアーティストが1つの作品に関わったり、想像よりずっと「多対多」でした。

ほかにもあった、小さな「困った」

3つには入りきらなかったものを、さらっと紹介します。

  • Amazonのクローラーでサーバーが詰まる:ヘルスチェック失敗のメールが来てログを見たら、Amazonbotが画像を次々に取りに来ていて、無料プランの小さなサーバーが手一杯に。API側のドメインでクロールを断る robots.txt を出し、同時にさばけるリクエスト数も増やしました。
  • 画像がセキュリティ設定でブロックされる:AdSenseを載せるために厳しめのCSP(読み込んでよいリソースの指定)を入れたところ、Wikimediaのアーティスト画像が表示されなくなりました。サーバー経由で配信するようにして解決。
  • 画像がないと「壊れている」ように見える:ジャケットのない作品は灰色の四角にしていましたが、エラーにしか見えない……。サイトのアイコンを淡くした「NO IMAGE」に変えました。
  • 取り込み途中だと「作品が少ない」と誤解される:MusicBrainzは1秒1回までというルールがあるので、作品の多いアーティストは取り込みに数分かかります。進み具合のバナーを出して、終わったら自動で更新するようにしました。
  • Next.js 15のサポート終了で、ある日突然デプロイできなくなる:Cloudflare向けのビルドツールが、サポートの終わったバージョンのビルドを止める仕様でした。急いでNext.js 16に移行しました。

まとめ:本番に出して初めて分かることがある

振り返ると、今回の3つには共通点がありました。どれもローカルでは問題なく動いていたことです。

困ったことローカルで気づけなかった理由
配信リンク失敗しても検索リンクが出るので、動いているように見えた
500エラーローカルのDBには、同時につなげる数の厳しい上限がなかった
曲・作品の奪い合い試していたアーティストが少なく、重なるデータがほとんどなかった

個人開発だと、「本番で使ってみて、おかしいところに自分で気づく」しかありません。だからこそ、毎日自分でサイトを触って、ログを眺めるのが一番のテストだと感じました。

おわりに

今回は「困ったこと」を紹介しました。どれも直した今は笑い話ですが、当時は少し焻りました😅

次回のテーマは 「大変だったこと」 です。月額0円で運用するためのサーバー選びや、広告を載せるまでの道のりについて書く予定です。

Release Orderはこちらから使えます。好きなアーティストの歩みを、ぜひたどってみてください。

👉 https://release-order.jp

コメント