Open9

Reposoup: MongoDBをマジでただのJson置き場として使ったらどうなるのか実験

okuokuokuoku

MongoDBを始めいわゆるNoSQLデータベースはunstructured dataの処理に長けていることを推していることが多い。

https://www.mongodb.com/unstructured-data

Platforms like MongoDB Atlas are especially well-suited for housing, managing, and using unstructured data.

... というかMySQLやSQLite3なりなんなりの伝統的なRDBMSでもXMLとかJsonを格納/パースする機能を直接的にサポートしている。

もっとも、現実的にはMongoDBであってもORMと組合せて運用されるようなケースが割と普通で、格納されるデータがある程度"設計"されているのが普通と言える。このため、MongoDBにJsonデータを格納する前にある程度加工する必要が生じる。

しかし、 原理的には 、未加工のデータをそのままMongoDBに突っ込み、MongoDB側の On-demand materialized view 上に"設計された"データを構築することで事前の加工を不要にできるのではないだろうか。というわけで実験してみる。

背景

手元のデータベースは全部 "GitリポジトリにYAMLを格納し、DBMS/NoSQLは単なるキャッシュ・インデックスとして運用する" という方針で作成している。で、現状はJavaScriptやSchemeで書かれた事前変換スクリプトがインデックス等に有利な形にした上でDBMS/NoSQLに格納している。

しかし、これだとインデックスの貼り方を変更したいときにデータを全て再処理する必要があってちょっとスケーラビリティに欠ける。このため、DBMS/NoSQLには可能な限り生のデータを格納する方向にしたい。

データ

データは以下の2種類のものを混在させて 1つの MongoDB collectionに格納する。

このデータを元に、一般的なWeb写真アルバムアプリ(Yamap https://yamap.com/search/activities みたいな奴)を構築できるか考えたい。

制約

MongoDBは(BSONで見たときに)16MiB以上のドキュメントを扱えない。現状手元には1つ200MiBを超えるようなJsonデータもあり、そのようなデータは事前加工でいくつかの "ページ" に分割している。これはどうやっても直接扱う方法が存在しない。MongoDBのストアドプロシジャとしてJsonパーサを書く。。とか。。?

↓ で扱っているGPS航跡のJsonは7MiBあり、実は既に結構あぶない事になっている。。

okuokuokuoku

XML → Json 変換

GPSロガーが出力するデータはGPXと呼ばれるXML形式のデータで、MongoDBはXMLを直接扱うことはできないので一旦可能なかぎりそのままJsonに変換する。

XML→Jsonへの変換はいくつか提案されているが、今回は厳密なroundtrip変換は不要なのでPythonの xmltodict を採用したxq(yq の一部)で変換する。

xq . 20221022-093936.gpx

変換後は良さげなJson形式になっていることがわかる。

    "trk": {
      "name": "Track 20221022-093936",
      "trkseg": {
        "trkpt": [
          {
            "@lat": "35.72977765",
            "@lon": "140.82728715",
            "ele": "52.398",
            "time": "2022-10-22T00:39:36Z",
            "speed": "0.000",
            "sat": "27"
          },
          {
            "@lat": "35.72978048",
            "@lon": "140.82729908",
            "ele": "49.931",
            "time": "2022-10-22T00:39:37Z",
            "speed": "0.000",
            "sat": "29"
          },

(ちなみに座標は 銚子駅付近 。)

ロケーション情報のXMLとしては、他にGoogle Earth等で利用されているKML https://ja.wikipedia.org/wiki/KML があるが、こちらは座標リストがテキスト形式なので今回のような目的には向かない。ただ良い方法が無いかは考えたいところ。。

okuokuokuoku

MongoDBのインストール

https://www.mongodb.com/try/download/community

まだWindows版が存在するので、とりあえずそれで。 C:\Program Files\MongoDB\Server\6.0\bin\mongod.cfg に設定ファイルがあるが、今回のインストールではlistenアドレスは localhost に設定されていた。

同時にインストールされるCompass (GUI)でデータベースとコレクションを作成する。

okuokuokuoku

設定の投入

更にコレクション config を作ってデータ投入スクリプトの設定を書く。

こういうの外部のファイルに書かせるかデータベースに突っ込むか悩みどころではある。今回は:

のを試してみることにする。あ、設定にはコレクション名も入れといた方が良いかな。

{
  "_id": {
    "$oid": "6362ca2c0ec88739dadeab39"
  },
  "files": [
    "metadata.json",
    "content.json",
    "gpx.xml",
    "kml.xml"
  ],
  "collection": "mapraw",
  "gitdir": "c:/cygwin64/home/oku/repos/photometa/.git"
}
okuokuokuoku

LIB 環境変数が設定されていると node-gyp に失敗する

何故。。PowerShellでVisual Studioのパスを検出するのに失敗する。

950 error gyp ERR! find VS could not use PowerShell to find Visual Studio 2017 or newer, try re-running with '--loglevel silly' for more details
okuokuokuoku

設定の読み取り

まぁQuick startの通りに。

https://www.mongodb.com/docs/drivers/node/current/quick-start/

const { MongoClient } = require("mongodb");
const uri = "mongodb://127.0.0.1:27017";
const client = new MongoClient(uri);

async function run(){
    try {
        const db = client.db("my_mapping");
        const col = db.collection("config");

        const config = await col.find({ _id: { $exists: true }})
        .toArray();
        console.log(config);
    } finally {
        await client.close();
    }
}

run().catch(console.dir);

find はカーソルを返却するので toArray で配列(を返却するプロミス)に変換する。

okuokuokuoku

Aggregationで撮影地点を抽出してみる

とりあえず、今年ぶんの写真のExifデータをJson化したものから、geojsonを生成してプロットしてみる。

すごい事になってんな。。

[{
 $match: {
  filename: 'metadata.json'
 }
}, {
 $project: {
  doc0: {
   $arrayElemAt: [
    '$doc',
    0
   ]
  }
 }
}, {
 $project: {
  gpsdate: '$doc0.GPSDateTime',
  gpslat: '$doc0.GPSLatitude',
  gpslong: '$doc0.GPSLongitude'
 }
}, {
 $match: {
  gpslat: {
   $type: 'double'
  },
  gpslong: {
   $type: 'double'
  }
 }
}, {
 $project: {
  location: {
   type: 'Feature',
   properties: {
    dummy: 'dummy'
   },
   geometry: {
    type: 'Point',
    coordinates: [
     '$gpslong',
     '$gpslat'
    ]
   }
  }
 }
}, {
 $group: {
  _id: null,
  features: {
   $push: '$location'
  }
 }
}, {
 $project: {
  type: 'FeatureCollection',
  features: '$features'
 }
}]

https://twitter.com/priokuoku/status/1588113552350810112