Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ljoin] $lookup не задействует индекс при работе с интервалами #7

Open
PlatonB opened this issue Apr 13, 2020 · 9 comments
Assignees
Labels
bug Something isn't working help wanted Extra attention is needed

Comments

@PlatonB
Copy link
Owner

PlatonB commented Apr 13, 2020

Вчера, по-сути, завершил проект high-perf-bio, реализовав в left_join отбор геномных интервалов одной MongoDB-коллекции по их вхождению/невхождению в интервалы из других коллекций БД. Но, как выяснилось, не без ложки дёгтя.

Пояснение для IT-специалистов, не специализирующихся на работе с генетическими данными. Что такое геномный интервал? Есть хромосомы - надмолекулярные сткуктуры, включающие, помимо всего прочего, ДНК. Есть нуклеотиды - мономеры ДНК. Сослаться на тот или иной участок ДНК можно так: обозначение хромосомы, номер стартового нуклеотида, номер конечного нуклеотида. Эти два нуклеотида - границы как раз геномного интервала. В биоинформатических таблицах чаще всего мы видим интервалы с выявленным влиянием на организм.

Aggregation pipeline из программы left_join, построенный по официальным докам MongoDB, выдаёт правильные результаты, но работает бесконечно долго для больших коллекций. Ни compound, ни одиночные индексы не ускоряют вычисление. Вот основа пайплайна - код левостороннего внешнего объединения коллекций - источника описываемой проблемы:

pipeline = [{'$lookup': {'from': right_coll_name,
                         'let': {'chrom': '$chrom', 'start': '$start', 'end': '$end'},
                         'pipeline': [{'$match': {'$expr': {'$and': [{'$eq': ['$$chrom', '$chrom']},
                                                                     {'$lt': [{'$max': ['$$start', '$start']},
                                                                              {'$min': ['$$end', '$end']}]}]}}}],
                         'as': right_coll_name.replace('.', '_')}} for right_coll_name in right_coll_names]

Эта же конструкция, но чуть упрощённая (Ilya Vorontsov):

pipeline = [{'$lookup': {'from': right_coll_name,
                         'let': {'chrom': '$chrom', 'start': '$start', 'end': '$end'},
                         'pipeline': [{'$match': {'$expr': {'$and': [{'$eq': ['$$chrom', '$chrom']},
                                                                     {'$lt': ['$$start', '$end']},
                                                                     {'$lt': ['$start', '$$end']}]}}}],
                         'as': right_coll_name.replace('.', '_')}} for right_coll_name in right_coll_names]

Каждый объединённый документ представляет собой документ "левой" коллекции, в который вложены отвечающие запросу документы "правых" коллекций. Если в "правой" коллекции не нашлось соответствий, в объединённый документ попадает пустой список.

Кто-нибудь знает, как решить/обойти проблему игнора индексов?

P.S. Я уже создал аналогичную тему на официальном форуме MongoDB, но там далеко не всегда отвечают. Поэтому очень надеюсь на помощь коллег, знакомых и других посетителей репозитория.

@VorontsovIE, @yustinaivanova, может у вас будут какие-то идеи? Был бы очень благодарен.

Предполагаемые тормозящие факторы:

  • Вложенный пайплайн, какое бы выражение в нём ни было. UPD: точно нет: если попробовать объединять аналогичной конструкцией не по интервалам, а по одиночным полям, то всё посчитается почти мгновенно.
  • Подсчёт максимальной стартовой границы и минимальной конечной (Ilya Vorontsov).
  • Сочетание работы по хромосомам с работой по интеравалам.
  • Добавляемая к $lookup-пайплайну сортировка. Сразу говорю, что она не влияет.
@PlatonB PlatonB added bug Something isn't working help wanted Extra attention is needed labels Apr 13, 2020
@PlatonB PlatonB self-assigned this Apr 13, 2020
@PlatonB
Copy link
Owner Author

PlatonB commented Apr 13, 2020

Если кто мыслит на SQL, пишите на SQL, я не растеряюсь. Придумаете что-нибудь под любую привычную вам СУБД - будет не менее полезно.

@yustinaivanova
Copy link

yustinaivanova commented Apr 14, 2020

Привет!
Странно что долго работает. Обычно SQL все быстрее выполняет.
Я к сожалению не специалист в MongoDB

@VorontsovIE
Copy link

Сорри, я не вполне разобрался в синтаксисе, но мне кажется, что у тебя операция min/max вынуждена пересчитывать границу для каждого объекта.
Если это так, то стоит убрать сложную логику из базы данных. Пусть она делает не полную операцию, но простую и быструю, а питон потом дофильтровывает оставшийся небольшой набор интервалов до итогового.

@PlatonB
Copy link
Owner Author

PlatonB commented Apr 14, 2020

Илья, Юстина, спасибо!

Если применить конструкцию from-let-pipeline-as не к chrom-start-end, а к какому-нибудь одному полю (например, name для тех же BED), то команда выполнится моментально.

pipeline = [{'$lookup': {'from': right_coll_name,
                         'let': {'name': '$name'},
                         'pipeline': [{'$match': {'$expr': {'$eq': ['$$name', '$name']}}}],
                         'as': right_coll_name.replace('.', '_')}} for right_coll_name in right_coll_names]

Это говорит о том, что само наличие pipeline, вложенного в $lookup, не виновато. Значит, тормозит либо упомянутая Ильёй операция min/max, либо обработка сразу по хромосомам и позициям.

PlatonB added a commit that referenced this issue Apr 14, 2020
Создан, в первую очередь, чтобы получить схожие по структуре и гарантированно содержащие пересекающиеся интервалы BED-файлы для диагностики #7. Естественно, эта прога потом пригодится для создания тестировочных файлов под многие другие задачи.
@PlatonB
Copy link
Owner Author

PlatonB commented Apr 16, 2020

Избавился от $max и $min.

pipeline = [{'$lookup': {'from': right_coll_name,
                         'let': {'chrom': '$chrom', 'start': '$start', 'end': '$end'},
                         'pipeline': [{'$match': {'$expr': {'$and': [{'$eq': ['$$chrom', '$chrom']},
                                                                     {'$and': [{'$lt': ['$$start', '$end']},
                                                                               {'$lt': ['$start', '$$end']}]}]}}}],
                         'as': right_coll_name.replace('.', '_')}} for right_coll_name in right_coll_names]

Та же хрень.

@VorontsovIE
Copy link

Не знаю...
Во-первых, попробуй избавиться от вложенных and-ов (and может принимать больше двух условий):

pipeline = [
    {"$lookup":
        {
            "from": right_coll_name,
            "let": {"chrom": "$chrom", "start": "$start", "end": "$end"},
            "pipeline": [
                {
                    "$match": {
                        "$expr": {
                            "$and": [
                                {"$eq": ["$$chrom", "$chrom"]},
                                {"$lt": ["$$start", "$end"]},
                                {"$lt": ["$start", "$$end"]}
                            ]
                        }
                    }
                }
            ],
            "as": right_coll_name.replace(".", "_")
        }
    }
    for right_coll_name in right_coll_names
]

Во-вторых, посмотри, работает ли отдельно условие на хромосому и отдельно на одну из позиций - так же как ты отдельно проверял условие на имя.
Возможно, у какой-то у колонок проблемы с индексом, но сейчас ты тестируешь сразу три колонки, а проверяешь, что проблем нет - на четвертой.

@PlatonB
Copy link
Owner Author

PlatonB commented Apr 17, 2020

После упразднения вложенного $and ничего не изменилось.

Отдельные условия. Оказывается, всё зависит от оператора.

  • Неравенство. [{'$match': {'$expr': {'$lt': ['$$start', '$start']}}}] - ничего не используется:
    Неравенство

  • Равенство. [{'$match': {'$expr': {'$eq': ['$$start', '$start']}}}] - многочисленные обращения к индексу start_1:
    Равенство

@VorontsovIE
Copy link

Очень интересно. Попробуй пару вещей:

  • имеет смысл упростить код: вместо $$start подставить константу, чтобы избавиться от этой сложности
  • попробовать поставить индекс первым операндом (заменив lt на gt). Вдруг монга не умеет обращать операторы сравнения почему-нибудь...
  • попробуй вообще минимальный запрос без мишуры с джойнами протестировать на использование индекса, а потом постепенно усложняй. Так ты поймешь, на каком этапе всё ломается.

@PlatonB
Copy link
Owner Author

PlatonB commented Apr 17, 2020

имеет смысл упростить код: вместо $$start подставить константу, чтобы избавиться от этой сложности

Проверил - не влияет.

попробовать поставить индекс первым операндом (заменив lt на gt). Вдруг монга не умеет обращать операторы сравнения почему-нибудь...

Тоже не работает.

попробуй вообще минимальный запрос без мишуры с джойнами протестировать на использование индекса, а потом постепенно усложняй. Так ты поймешь, на каком этапе всё ломается.

Запросы без агрегации точно индексы используют. И сортировка тоже (там, правда, свои тараканы - #5).

PlatonB added a commit that referenced this issue Apr 18, 2020
- Уменьшил вложенность в пайплайне объединения интервалов. Issue #7 это, к сожалению, не решает. Новый код пусть будет отправной точкой для дальнейшего исследования проблемы.
PlatonB added a commit that referenced this issue Apr 21, 2020
- VCF: пересечение/вычитание по хромосоме и позиции. Кстати, омерзительный баг #7 в этом случае не наблюдается. Он только работу с интервалами затрагивает.
- Поддержка квазизначения глубины - 0, интерпретируемого как равенство количеству правых коллекций.
- Принт с окончательным значением глубины.
- Улучшение справки.
PlatonB added a commit that referenced this issue Dec 1, 2020
- Смелый эксперимент с пересечением по локации. К сожалению, скорость сильно зависит от сочетания форматов, а также от размера исходной таблицы и коллекции. Проблема напоминает пресловутый баг #7, но при интервальном пересечении прогой annotate точно задействуется составной индекс (#CHROM_1_POS_1 или chrom_1_start_1_end_1).
- Протащил сюда все новшества query 3.х, в частности, projection.
- Конечные метастроки теперь более VCF-way.
- Принты на англоязе как первый шаг подготовки к статье.
PlatonB added a commit that referenced this issue Dec 18, 2020
- Left join по локации как отдельная опция с предупреждением об её экспериментальном (см. #7) статусе.
- Опция отбора полей (projection).
- Огромный рефакторинг.
- Переработанный генератор конечных метастрок.
- По-новому строятся имена output-файлов.
- Замер времени выполнения основного кода.
- Ещё более подробная теория левых и правых коллекций в приветственном принте.
- Глубина теперь - не глубина, а охват.
- Если в БД меньше 2 коллекций - программа упадёт не через sys.exit(), а более цивилизованно - путём вызова исключения.
- Принты на английском.
@PlatonB PlatonB changed the title $lookup не задействует индекс при работе с интервалами [left_join] $lookup не задействует индекс при работе с интервалами Sep 14, 2021
@PlatonB PlatonB changed the title [left_join] $lookup не задействует индекс при работе с интервалами [ljoin] $lookup не задействует индекс при работе с интервалами Jan 19, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants