inventory: Optimized data structure
Проблема
На настоящий момент времени дополнительные данные в inventory хранятся в поле data
, которое представляет собой словарь со вложенными словарями.
Первый уровень вложенности - model interface, второй - атрибут. Например, значения geopoint.x
и geopoint.y
хранятся как:
"data": {
"geopoint": {
"x": XXX,
"y": YYY
}
}
У данной структуры данных есть ряд недостатков:
- Поле, фактически, не структурировано
- Поиск не индексируется
- Нет возможности создать несколько экземпляров одного интерфейса (может быть полезным для привязки к разным адресным базам)
- Название аттрибута не может содержать некоторые специальные символы (точка, etc)
Предлагаемое решение
В Object
заменить поле:
data = DictField()
На
data = ListField(EmbeddedDocumentField(ObjectAttr))
где:
class ObjectAttr(EmbeddedDocument):
interface = StringField()
attr = StringField()
value = DynamicField()
scope = StringField()
И создать дополнительный индекс:
coll.create_index([("data.interface", 1), ("data.attr", 1), ("data.value", 1)])
Проверка запросов
Запросы вида
coll.find({"data": {"$elemMatch": {"interface": "address", "attr": "id", "value": "XXX"}}})
корректно индексируются:
{'queryPlanner': {'plannerVersion': 1,
'namespace': 'noc.noc.objects',
'indexFilterSet': False,
'parsedQuery': {'data': {'$elemMatch': {'$and': [{'attr': {'$eq': 'id'}},
{'interface': {'$eq': 'address'}},
{'value': {'$eq': 'XXX'}}]}}},
'winningPlan': {'stage': 'FETCH',
'filter': {'data': {'$elemMatch': {'$and': [{'interface': {'$eq': 'address'}},
{'attr': {'$eq': 'id'}},
{'value': {'$eq': 'XXX'}}]}}},
'inputStage': {'stage': 'IXSCAN',
'keyPattern': {'data.interface': 1,
'data.attr': 1,
'data.value': 1},
'indexName': 'data.interface_1_data.attr_1_data.value_1',
'isMultiKey': True,
'multiKeyPaths': {'data.interface': ['data'],
'data.attr': ['data'],
'data.value': ['data']},
'isUnique': False,
'isSparse': False,
'isPartial': False,
'indexVersion': 2,
'direction': 'forward',
'indexBounds': {'data.interface': ['["address", "address"]'],
'data.attr': ['["id", "id"]'],
'data.value': ['["XXX", "XXX"]']}}},
'rejectedPlans': []},
'executionStats': {'executionSuccess': True,
'nReturned': 1,
'executionTimeMillis': 12,
'totalKeysExamined': 1,
'totalDocsExamined': 1,
'executionStages': {'stage': 'FETCH',
'filter': {'data': {'$elemMatch': {'$and': [{'interface': {'$eq': 'address'}},
{'attr': {'$eq': 'id'}},
{'value': {'$eq': '78070'}}]}}},
'nReturned': 1,
'executionTimeMillisEstimate': 10,
'works': 2,
'advanced': 1,
'needTime': 0,
'needYield': 0,
'saveState': 0,
'restoreState': 0,
'isEOF': 1,
'docsExamined': 1,
'alreadyHasObj': 0,
'inputStage': {'stage': 'IXSCAN',
'nReturned': 1,
'executionTimeMillisEstimate': 10,
'works': 2,
'advanced': 1,
'needTime': 0,
'needYield': 0,
'saveState': 0,
'restoreState': 0,
'isEOF': 1,
'keyPattern': {'data.interface': 1,
'data.attr': 1,
'data.value': 1},
'indexName': 'data.interface_1_data.attr_1_data.value_1',
'isMultiKey': True,
'multiKeyPaths': {'data.interface': ['data'],
'data.attr': ['data'],
'data.value': ['data']},
'isUnique': False,
'isSparse': False,
'isPartial': False,
'indexVersion': 2,
'direction': 'forward',
'indexBounds': {'data.interface': ['["address", "address"]'],
'data.attr': ['["id", "id"]'],
'data.value': ['["78070", "78070"]']},
'keysExamined': 1,
'seeks': 1,
'dupsTested': 1,
'dupsDropped': 0}},
'allPlansExecution': []},
'operationTime': Timestamp(1597736262, 1)}
Множественные варианты интерфейсов
При интеграции с несколькими внешними базами может понадобиться множественность атрибутов, когда один и тот же интерфейс может повторяться несколько раз с разными значениями. Нвпример, интерфейс address
может встречаться несколько раз, представляя различные взгляды на различные базы. В таком случае следует использовать дополнительное поле scope
:
[
{"interface": "address", "attr": "id", "value": "123", "scope": "google"},
{"interface": "address", "attr": "text", "value": "google address, 1", "scope": "google"},
{"interface": "address", "attr": "id", "value": "321", "scope": "yandex"},
{"interface": "address", "attr": "text", "value": "yandex address, 15", "scope": "yandex"},
]
Переделка
- Миграция на новый формат прямолинейна.
- После миграции необходимо удалить старые индексы
- Методы
Object.get_data()
иObject.set_data()
остаются как есть, добавляется новый опциональный аргументscope
- Запросы вида
data__
иdata.
необходимо переделать на новый формат (20-30 мест)