Reposoup: MongoDBをマジでただのJson置き場として使ったらどうなるのか実験
MongoDBを始めいわゆるNoSQLデータベースは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に格納する。
- 写真・動画のExifデータ(撮影日時やGPS座標)のJson -- exiftoolで抽出した奴
- GPS航跡データ(1秒ごとにサンプリングしたGPS座標のリスト) -- 元データはGPX形式( https://ja.wikipedia.org/wiki/GPX )のXML
このデータを元に、一般的なWeb写真アルバムアプリ(Yamap https://yamap.com/search/activities みたいな奴)を構築できるか考えたい。
制約
MongoDBは(BSONで見たときに)16MiB以上のドキュメントを扱えない。現状手元には1つ200MiBを超えるようなJsonデータもあり、そのようなデータは事前加工でいくつかの "ページ" に分割している。これはどうやっても直接扱う方法が存在しない。MongoDBのストアドプロシジャとしてJsonパーサを書く。。とか。。?
↓ で扱っているGPS航跡のJsonは7MiBあり、実は既に結構あぶない事になっている。。
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 があるが、こちらは座標リストがテキスト形式なので今回のような目的には向かない。ただ良い方法が無いかは考えたいところ。。
MongoDBのインストール
まだWindows版が存在するので、とりあえずそれで。 C:\Program Files\MongoDB\Server\6.0\bin\mongod.cfg
に設定ファイルがあるが、今回のインストールではlistenアドレスは localhost
に設定されていた。
同時にインストールされるCompass (GUI)でデータベースとコレクションを作成する。
設定の投入
更にコレクション config
を作ってデータ投入スクリプトの設定を書く。
こういうの外部のファイルに書かせるかデータベースに突っ込むか悩みどころではある。今回は:
- libgit2で直接Gitリポジトリを読んで投入する
- xml-jsのcompact notation https://www.npmjs.com/package/xml-js も一緒に投入して比較する
のを試してみることにする。あ、設定にはコレクション名も入れといた方が良いかな。
{
"_id": {
"$oid": "6362ca2c0ec88739dadeab39"
},
"files": [
"metadata.json",
"content.json",
"gpx.xml",
"kml.xml"
],
"collection": "mapraw",
"gitdir": "c:/cygwin64/home/oku/repos/photometa/.git"
}
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
設定の読み取り
まぁ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
で配列(を返却するプロミス)に変換する。
とりあえず適当にisomorphic-gitで実装
なんかnodegitのインストールが上手くいかなかったので。。
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'
}
}]
Json as everything
例えばTailscaleは自社のDBを1つのjsonファイルで持っていた。
これがetcdになった後、今年に入ってからSQLite + litestreamに代わっている。