記事を生成する処理自身のログが、その記事の「事実の出典」に混ざっていました。原因は、記事の材料を集める処理が、運営イベントの記録を区別せずに読み込んでいたことです。
記事の根拠として渡す情報は、記事生成とは別の場所から集めるものだと思い込んでいました。しかし、生成処理が記録を書き込む先と、事実を集める先が同じなら、処理自身の技術的な記録も候補に入ります。今回は、その自己参照が実際に起きていました。
事実の出典に、生成処理のログが入る
noteの記事を作る処理では、その日に起きた出来事を集め、記事の根拠として使います。この根拠情報が、記事のfact_sourcesです。記事の内容が実際の出来事に基づいているかを支えるため、ここに入るデータの性質は重要です。
問題になったのは、日々の運営記録を集める処理が、operational_eventsテーブルの全行を無条件に事実情報として取り込んでいたことでした。このテーブルには運営上の出来事だけでなく、note生成処理が出力した技術的なメタ情報のログも含まれていました。
その結果、記事を作るための処理が、自分自身について書き残したログを、記事の事実の候補として読み込む状態になっていました。ログが記録されること自体が問題なのではありません。運営の動きを追うためのログと、記事の根拠として使う情報を、同じ条件で扱っていたことが問題でした。
テーブル名だけではデータの意味を判断できない
原因を追うと、データの取得処理に、行の用途を分ける条件がありませんでした。operational_eventsという名前からは運営上の出来事を集める場所に見えますが、実際には記事生成の内部処理に関するログも入っていました。
テーブルを参照しているからといって、その中のすべての行が同じ用途に適しているとは限りません。ログとして保存することと、記事の根拠として採用することは、別の判断です。今回の取得処理では、その境界がコード上にありませんでした。
この状態では、生成処理の動作を記録するたびに、その記録が次の生成時に事実候補として再び読み込まれる可能性があります。ログが事実として本文に採用されたとまでは確認できていませんが、少なくとも根拠情報に混入する経路はできていました。
生成処理自身のメタ情報を除外する
修正では、note生成に関するメタ情報のログを、記事の事実情報から除外しました。この変更は、note生成のメタログを事実から取り除く修正として記録されています。
ここで大切なのは、ログを保存する仕組みそのものをなくすことではありません。運営状態を追跡するための記録は必要です。一方で、その記録を記事の根拠に使うかどうかは、用途に応じて別に制御する必要があります。記録先に存在することだけを理由に、すべてを事実として扱わないようにしました。
別のログ設計にも影響した
この発見は、後続のサーバーのライフサイクル記録を設計する際の判断にも影響しました。そのタスクでは、operational_eventsをログの保存先として使わない方針になりました。
今回の問題を通じて分かったのは、データの混入を防ぐ方法は、読み込む側で除外条件を加えることだけではないということです。書き込む先を分ければ、あとから用途を判定する負担や、別の処理が意図せず読み込む可能性を抑えられます。今回の設計判断では、事実の出典にログが混ざる経路を作らないことを優先しました。
「事実の出典」として使うデータソースには、生成対象の処理自身が書いたログを無条件に含めてはいけません。ログの保存場所と、記事の根拠に使う情報の境界を、取得側と保存先の両方で意識する必要がありました。