はじめに
FIELDNOTE は小型機材キットを貸し出しています。引き継いだ MongoDB カタログには、利用できる在庫数と、古い文字列や不確かな値が混在しています。既知の在庫数ゼロは貸出不可を意味しますが、数量の欠落は店が把握していないことを意味します。両方をゼロにするとデータの問題が隠れます。これらを分け、信頼できないレコードを後の仕入先確認のために保全することが仕事です。
再利用可能な分類ポリシーと、アクティブなコレクションから削除する前に完全な元ドキュメントを保管する隔離スクリプトを保存します。基本的なターミナル操作、MongoDB ドキュメント、入れ子のフィールド、find、通常の更新の知識が必要です。ここで使う JavaScript、スキーマルール、BSON の詳細は使用前に説明します。MongoDB、シェル、架空のカタログは準備済みです。全編を通して Terminal を使います。所要時間は読解と通常のミスを含む作者の見積もりです。
/home/labex/project/catalog-desk に policy.js と quarantine.js を作成します。元のすべての ID、完全な元フィールドと型、無関係な拡張情報、コレクションの同一性、提供されたツールを保全してください。この実習では有効なレコードと修復可能なレコードを変更しません。コレクションの削除・再構築や、既存の隔離元データの上書きはしません。このコースのカタログだけを扱います。同時書き込みのない制御された保守時間の作業です。コピーと削除は別操作であり、全カタログを覆うトランザクションではありません。
引き継いだカタログを読み取り分類する
このステップでは、通常の使えるレコードを読み、保存された数量の型を区別し、明示的な分類ポリシーを保存します。Terminal から始めます。cd はディレクトリを移動し、この絶対パスは誤ったディレクトリからの復帰にも使えます。
cd /home/labex/project/catalog-desk
./catalog は、このディレクトリの提供済み接続ランナーです。./ はローカルの実行ファイルを選びます。show は実際に保存されたドキュメントを読みます。ドキュメント ID を 1 つ渡します。
./catalog show KIT-101
name: "Tripod"、stock.rack: "A"、"$numberInt": "12" を含む stock.available を探してください。出力は、明示的な BSON 型タグを持つテキスト表現の正規形式の Extended JSON です。BSON は MongoDB の保存ドキュメント形式です。$numberInt は 32 ビット整数、$numberLong は 64 ビット整数を示します。出力のタグは保存された数値の説明であり、追加すべき普通の入れ子の在庫フィールドではありません。
店は、0~50、両端を含む在庫数にどちらの整数型も認めます。元の stock.rack と supplier は有用な情報なので残します。_id がレコードを識別し、Tripod という同名の 2 レコードも別物です。
すべての数量について、サーバーが示す実際の型を確認します。
./catalog types
このリーダーは stock.available に MongoDB の集計 $type 式を使います。actualType 列は int、long、string、null、missing、array、double を区別します。double は浮動小数点の BSON 型で、整数に見える Double の 12 でも、整数だけという店の契約の範囲外です。missing はフィールドがないこと、null は明示的な null 値があることを意味します。[12] のような配列は、単一のスカラー数量ではありません。
カタログ全体を読み、型と元レコードを対応させます。
./catalog show
クエリを書く前に、この判断表を使います。
| 保存された数量 | この実習での判断 | 理由 |
|---|---|---|
| Int32 12 または Long 0 | 使える値として保持 | 既知のゼロも含む、有効なスカラー整数 |
文字列 "12" または "0" |
承認済み修復のため保持 | 合意した範囲の正規の十進表現 |
"12 kits"、"012"、"51" |
隔離 | 意味が不明、未承認の表現、または範囲外 |
| Null、欠落、配列、Double 12 | 隔離 | 合意したスカラー整数の契約を満たさない |
修復可能な文字列は、0~50 の数値の正規 ASCII 数字だけで、符号、空白、先頭ゼロ、小数点、単位接尾辞を含みません。これは引き継いだデータに対する明示的な合意であり、数値に見える全文字列を安全に変換できるという保証ではありません。隔離とは、確認のため完全な元データを残すことで、代わりの数量を作ることではありません。
この後も同じ Terminal とディレクトリを使います。ファイル名のミスでは、ls で実際の名前を確認し、つづりを直して再実行します。ファイルエラーはデータベースの拒否ではなく、環境のリセットも不要です。
分類ポリシーを保存する
MongoDB セレクターは、一致するレコードを記述するドキュメントです。$jsonSchema は形と BSON 型を検査します。required は必須フィールドを指定しますが、それだけで値を非 null にしません。properties は各フィールドを記述し、bsonType は BSON 型を制約し、minimum/maximum は境界値を含みます。入れ子の stock ルールは別途 available を要求します。言及しないフィールドは許されるため、rack、supplier、後の拡張も残せます。
ここでは JSON Schema のスカラー型ルールを使います。{"stock.available": {$type: "int"}} のような通常のクエリ述語は、整数を含む配列にも一致し得ます。それでは、このカタログに必要なスカラー数量を確認できません。
MongoDB シェルの mongosh 用 JavaScript を書きます。const は値に名前を付け、= は初期値を代入し、波括弧はオブジェクト、角括弧は配列、カンマは要素の区切りです。引用符付きは文字列、引用符なしの 0 と 50 は数値です。セミコロンで文を終えます。schema は有効なレコード全体を記述し、valid は MongoDB セレクターとして包みます。
承認文字列のポリシーは、数量ルールだけを変えた同じ形です。JSON.stringify はこの通常のポリシーオブジェクトを JSON テキストにし、JSON.parse は別コピーを読みます。その後コピーを変えても元のポリシーは変わりません。これは通常のポリシー用で、保存済み BSON レコードの保全方法ではありません。stringSchema.properties.stock.properties.available は置換する入れ子のルールを参照します。
pattern は許可するテキストの正規表現です。^ は先頭、\z は文字列の絶対末尾を表します。| は選択肢を分け、[1-9] は 1~9 の 1 桁、[1-4][0-9] は 10~49 の 2 桁です。0 と 50 を合わせると、承認範囲を厳密に表します。JavaScript 文字列はバックスラッシュをエスケープに使うため、ソースでは \\z と書いて MongoDB に \z を渡します。この絶対末尾のアンカーは最後の改行も拒否します。通常の末尾アンカー $ では、その改行の前の一致が許される場合があります。$nor: [valid, repairable] は、どちらにも一致しないレコードを選びます。
ヒアドキュメントでポリシーファイルを作ります。cat > policy.js は以下を書き、以前の内容を置き換えます。引用符付き <<'POLICY' は、ドル記号などの JavaScript テキストをシェルでそのまま保ちます。単独行の POLICY で終え、完全なブロックをコピーします。
cat > policy.js <<'POLICY'
const schema = {
bsonType: "object",
required: ["_id", "name", "stock"],
properties: {
_id: { bsonType: "string", minLength: 1 },
name: { bsonType: "string", minLength: 1 },
stock: {
bsonType: "object",
required: ["available"],
properties: {
available: { bsonType: ["int", "long"], minimum: 0, maximum: 50 }
}
}
}
};
const valid = { $jsonSchema: schema };
const stringSchema = JSON.parse(JSON.stringify(schema));
stringSchema.properties.stock.properties.available = {
bsonType: "string",
pattern: "^(0|[1-9]|[1-4][0-9]|50)\\z"
};
const repairable = { $jsonSchema: stringSchema };
const bad = { $nor: [valid, repairable] };
POLICY
提供された分類器は読み取り専用のデータベース権限で、この保存ファイルを変更せず評価します。その後、valid、repairable、bad を実際の MongoDB クエリに使います。
./catalog classify policy.js
件数だけでなく、実際のドキュメント群を確認します。valid は元の Long のゼロを含む KIT-101 と KIT-102、repairable は元の文字列のままの KIT-103 と KIT-104 を含むはずです。outside は、元の null、欠落フィールド、配列、Double を含む KIT-105~KIT-111 です。KIT-105 の仕入先確認用の拡張全体も見える必要があります。
JavaScript 構文エラーが出たら、ファイルは動作するポリシーとして受け入れられていません。完全なヒアドキュメントで壊れたファイルを置き換え、分類器を再実行します。構文エラーはファイル・式の問題であり、接続不能は別のエラーです。分類結果に一致がなかった意味ではありません。この分類器でデータの修復・削除は行われません。
不確かなレコードを削除する前に元データを保全する
このステップでは、隔離スクリプトを保存し、最初のコピー後の実際の中断を観察し、同じスクリプトを再実行して完了します。Terminal の /home/labex/project/catalog-desk から始めます。
隔離ドキュメントは元の _id、完全な保存レコードを含む original、reason: "outside-approved-catalog-contract" を持ちます。理由は仕入先確認が必要なことを示し、在庫ゼロを断定しません。元の数量は欠落、null、配列、別の型かもしれず、表示値や名前だけのコピーでは証拠が失われます。
別の境界もあります。シェルは、保存 BSON 数値を読み取る際、通常の JavaScript 数値に変換する場合があります。整数に見える JavaScript 数値を保存すると、元が Double でも Int32 になることがあります。元データのすべてのコピーと比較には、find/findOne の第 3 引数 {promoteValues: false, bsonRegExp: true} を使います。JavaScript 相当に変換せず、BSON 数値ラッパーと正規表現値を保ちます。第 2 引数 {} は全フィールドを残す空の投影です。EJSON.stringify(value, {relaxed: false}) は、保持した型から正規形式の Extended JSON を作るので、比較中も Double 12 と Int32 12 を区別できます。
スクリプトのループは対象外の集合を 1 回読み、各レコードを別々に扱います。sort({_id: 1}) は読みやすい ID 昇順、toArray() は結果を配列にまとめます。for (const doc of ...) は結果ごとに中の文を繰り返します。upsert は元の ID で隔離レコードを探し、upsert: true はなければ作成します。$setOnInsert は作成時だけ元データと理由を書き、既存コピーは変えません。findOne は保存コピーを読み戻します。
if は条件を調べます。!== は不一致、|| はどちらかが真であることを表します。元データや理由が違えば throw Error(...) で、その元レコードを削除する前に非ゼロで停止させる必要があります。deleteOne は検証済みの元 ID だけを削除します。それ以前のレコードは完了済みかもしれません。個別操作であり、全件成功か全件取消しかというバッチではありません。同時書き込みがなければ、各削除前の完全比較の成功が、ここで使う再試行経路になります。
スクリプトを書きます。QUARANTINE ヒアドキュメントは、先ほどと同じ、そのまま書き込む構文です。
cat > quarantine.js <<'QUARANTINE'
const typed = { promoteValues: false, bsonRegExp: true };
for (const doc of db.equipment.find(bad, {}, typed).sort({_id: 1}).toArray()) {
db.quarantine.updateOne(
{_id: doc._id},
{$setOnInsert: {original: doc, reason: "outside-approved-catalog-contract"}},
{upsert: true}
);
const saved = db.quarantine.findOne({_id: doc._id}, {}, typed);
if (EJSON.stringify(saved.original, {relaxed: false}) !== EJSON.stringify(doc, {relaxed: false}) ||
saved.reason !== "outside-approved-catalog-contract") {
throw Error("Existing quarantine copy differs; keep source and investigate");
}
db.equipment.deleteOne({_id: doc._id});
}
QUARANTINE
最初は提供された copy-only 接続を使います。読み取り・コピーは許可し、削除は拒否する実際のデータベース権限です。ランナーは 1 回のシェル呼び出しで policy.js、次に quarantine.js をそのまま評価します。最初の削除で、ネイティブな権限エラーと非ゼロ終了を想定してください。
./catalog copy-only policy.js quarantine.js
メッセージには not authorized と、equipment に対する delete コマンドが含まれます。拒否前にコピーは成功しています。両方の実レコードを読みます。
./catalog archive KIT-105
./catalog show KIT-105
KIT-105 は両方に存在します。隔離元データには在庫テキストと extension 全体が、数値・日付・ObjectId の型タグを含めて必要です。アクティブな元データもそのままです。コマンド失敗は、以前の全書き込みが取り消された意味ではありません。
通常のコース用書き込み接続で、同じ保存ファイルを再実行します。
./catalog run policy.js quarantine.js
成功したスクリプトに出力は不要です。最初の既存コピーは検証して保持し、対応する元レコードを削除します。残りの不確かなレコードも、順にコピー・検証・削除します。実際の結果を読みます。
./catalog show
./catalog archive
アクティブなカタログは、修復待ちの承認文字列 2 件を含め、KIT-101~KIT-104 だけが不変で残るはずです。隔離先は、完全な元ドキュメントと合意した理由を持つ KIT-105~KIT-111 を含みます。特に KIT-109 の元の Double はまだ $numberDouble と表示され、KIT-107 の元データにはまだ available がありません。
移行を再実行します。
./catalog run policy.js quarantine.js
./catalog archive
移す対象外レコードはもうないので、保存した元データは変わりません。これは冪等な再実行です。完了した操作を繰り返しても業務結果は変わりません。コピー後の中断も、アーカイブキーが重複コピーを防ぎ、比較が不整合な既存コピーを拒否するため再開できます。競合エラーが出たら、元レコードと前のアーカイブを調査のため残します。上書きや元レコードの削除で「修正」してはいけません。スクリプトは読み取りと削除の間の同時編集に対する安全性を確立せず、バッチ全体のロールバックもできません。
まとめ
再利用可能な MongoDB ポリシーで、使える整数の数量、明示的に承認された文字列、不確かなレコードを分けました。その後、型を保持した完全な元データを保管してからアクティブな元レコードを削除し、実際の部分失敗を観察して、同じ保存済みスクリプトを再開しました。カタログには使えるレコードと修復可能なレコードが残り、隔離先には仕入先確認の根拠が残っています。変換と将来の書き込み検証は、次の独立した単元で行います。



