Pull to refresh

Comments 24

Крутая статья, спасибо за детальный разбор с PostgreSQL и daterange — это реально полезный подход для реляционных баз, особенно когда нужно гарантировать целостность данных на уровне БД. Но давай честно: SQL здесь мягко говоря не самый оптимальный вариант, особенно если задача — максимизировать скорость чтения и минимизировать место, а не мучаться с обновлениями и индексами. Я бы предложил перейти на key-value базу данных с предвычисленными данными по дням и использованием Roaring Bitmaps для сжатия множеств активов. Это как раз то, что позволит выжать максимум производительности без компромиссов.

Давай сравню наглядно твой подход с PostgreSQL (на основе твоих тестов) и альтернативный:

По времени исполнения запросов:

- В PostgreSQL: для 333 активов на дату — 3 мс (с BTree-индексом), но это с join'ами, сканированием интервалов и ORDER BY/LIMIT. Если список активов вырастет до 1000, или добавятся фильтры — время легко уйдёт в 5-10 мс+ из-за overhead'а индексов.

- В key-value с Roaring: один точечный lookup по ключу (дню) — <1 мс даже для 1000 активов. Почему? Данные предвычислены: загружаешь один blob (~2-3 КБ), десериализуешь в массив из 100 битмапов, и для каждого актива просто проверяешь contains() в O(1). Нет join'ов, нет сканирования — чистая скорость, как в кэше.

По объёму занимаемого места:

- В PostgreSQL: 1.27 млн записей + индексы (GIST и BTree) — легко 100-200 МБ+ на диске, особенно с overhead'ом на строки, даты и проверки целостности. Индексы сами по себе жрут место и замедляют вставки.

- В key-value с Roaring: вся история (~9500 дней) укладывается в <30 МБ. Каждый день — один ключ с сжатым blob'ом из 100 Roaring Bitmaps (по 20-30 байт на площадку, т.к. в среднем 10 активов на ней). Сжатие Roaring бьёт любые таблицы: нет дублирования дат, нет строк — чистые биты.

По сложности более сложных фильтров:

- В PostgreSQL: базовый запрос на дату — ок, но если добавить фильтры (например, "активы на площадках X и Y за период" или "пересечение с другими условиями")? Придётся городить подзапросы, window functions или даже материализованные views — это усложняет код, замедляет (время вырастет в разы) и требует тюнинга индексов. Целостность круто, но для чтения-ориентированной задачи это overkill.

- В key-value с Roaring: фильтры — это нативные операции над битмапами! Например, пересечение (intersection) двух площадок — bitmap1 & bitmap2 в O(N), где N маленькое. Хочешь активы на нескольких площадках за день? Просто загрузи данные дня и сделай bitwise ops. Для периода — загрузи несколько дней (параллельно, если нужно) и union/intersect. Это проще в коде, быстрее (миллисекунды) и масштабируемо на аналитику без перестройки индексов. Плюс key-value даёт преимущества в поиске: атомарные get'ы без блокировок, лёгкая репликация и кэширование, а Roaring позволяет делать сложные set-операции (union, difference) на лету, чего в SQL без костылей не добьёшься.

В общем, если твоя задача — не частые обновления, а быстрые чтения (как в аналитике или отчётах), то SQL здесь выглядит архаично: медленнее, жирнее и сложнее в расширении.

Не имею ничего против NoSQL баз данных. Сам использую CH, Redis (ValKey), Cassandra.

Давай сравню наглядно твой подход с PostgreSQL (на основе твоих тестов) и альтернативный

Если сравнивать, то для этого нужно сначала реализовать то, что с чего начиналась статья и задача. А именно, обеспечить гарантированное отсутствие пересечений диапазонов при конкурентной модификации данных разными процессами с соблюдением принципов ACID. Без этого говорить о производительности выборки, по меньшей мере, преждевременно.

Ок, понял, ты прав насчёт ACID и конкурентных обновлений — если история меняется онлайн с разных сторон, то PostgreSQL с exclusion constraint'ами реально надёжнее всего, и спорить глупо.

Но в 90% случаев с учётом спецтехники/ОС обновления приходят батчами раз в день/неделю из 1С или ERP, а днём только читают тоннами. Вот тут key-value с предвычисленными битмапами просто рвёт PostgreSQL:

Чтение: <1 мс на 1000 активов против 5–15 мс в твоём тесте.

Размер: 25–30 МБ против 150–200+ МБ с индексами.

Сложные вопросы типа «кто был на площадках 10–15 за месяц» — пара операций над битмапами, без подзапросов и window-функций.

Короче, если у вас обновления не онлайн и не критичны по секундам — делайте предрасчёт в key-value, будет в разы быстрее и легче. Если же реально конкурентная запись — оставайтесь на Postgres, там всё честно и без сюрпризов.

Нейросетка?

  Я бы предложил перейти на key-value базу данных с предвычисленными данными по дням 

Сейчас надо такие данные, через неделю приходит начальник и говорит мне нужен отчёт по другому срезу данных... И все эти предрасчитанные данные не пригаждаются и приходится перелопачивать сырые не структурированные данные.

Реляционная БД даёт ожидаемое(контролируемое) время в расчётах.

С другой стороны если кидать в nosql, то можно не париться структурой и какие данные нужны, какие нет... Всё в кучу и потом разгребать по мере необходимости по нужным требованиям. Но долго и не гарантированно (на тот период не сохраняли данные такие, а узнать о этом не можем при расчёте). И в результате будет выбрано решение с "кидаем всё в кучу и отдельно БД с расчётами обсчитанными на леру или по расписанию" откуда и будут браться результаты.

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

Ты в точку попал — если требования прыгают каждую неделю ("а теперь по моделям", "а теперь по водителям", "а теперь по пробегу"), то предрасчёт только по "актив → площадка → день" может оказаться бесполезным, и придётся либо всё перестраивать, либо держать сырые интервалы.

Но тут как раз фишка в том, что если хранить сырые интервалы в обычной таблице (как в статье), а предвычисленные снэпшоты по дням — в key-value, то ты получаешь и то, и другое:

Сырые интервалы — для гибкости, когда начальник придумал новый срез (добавляешь поле в таблицу или отдельную табличку — и всё).

Предвычисленные битмапы — только для самого горячего запроса ("где был список активов на дату"), который обычно 90% нагрузки и должен летать.

Обновляешь битмапы раз в сутки батчем — и никаких проблем с новыми требованиями, потому что сырые данные остаются на месте. А если новый срез тоже стал горячим — просто добавляешь ещё один слой предрасчёта (битмапы по новому измерению).

По времени расчётов — да, в реляционке оно предсказуемое, но в key-value для типичного запроса оно просто на порядок меньше и тоже предсказуемое (один get + пару микросекунд на битмапы).

Короче, я не за "бросаем всё в NoSQL и в кучу", а за гибрид: сырые данные в Postgres/CH (где удобно), а горячие ежедневные снэпшоты — в key-value для скорости. Так и начальник доволен (новые срезы быстро добавляются), и пользователи не ждут по 10 мс на каждый отчёт :)

А автор как раз показал крутой инструмент для сырых данных — респект ему за это! Просто я добавил вариант, как выжать ещё больше скорости из самого частого сценария. Всё от задачи, как ты и сказал 👍

предвычисленные снэпшоты по дням

А если не упрощать задачу, как сделал я в статье, а выполнять реальные бизнес требования, когда вместо дат - время с дискретностью до минуты, а этих основных средств - около 100 тысяч, если учитывать так же управляемые по доверенности?

Обновляешь битмапы раз в сутки батчем

Для примера, история дислокаций у нас занимает около двух терабайт. Не слишком ли неэффективно обновлять её каждые сутки? Особенно с учётом того, что оперативный доступ к ней требуется в режиме 24/7.

К тому же, каждая запись у нас не ограничена основным средством, площадкой и периодом, как в описанном очень упрощённом примере. А имеет еще более 70 аналитик, по которым требуется фильтрация или агрегация.

Проблему изменений требований для аналитики у нас замечательно решает CH. Но он не пригоден для оперативных отчетов, которым нужны актуальные и консистентные данные из оперативной ACID БД.

Даже не хочется читать этот комментарий, т.к. уж оооочень похож на copy-pastе беседу с ИИ

Есть ощущение, что всё, кроме btree в PG сделано так себе)))

Я специально, хоть и под спойлером, объяснил, что без GIST гарантировать отсутствие пересечения диапазонов было бы проблематично. Возможно, для каких-то задач и допустимо ограничивать конкурентный доступ к БД блокировками, рискуя получить deadlock, но уж точно не для всех.

У меня несколько иной вопрос: стоит ли вообще обращать внимание на разницу в скорости при таких абсолютных цифрах, если (по моим оценкам) рост времени исполнения в зависимости от рамеров линейный, и на production-ready размерах "возможны варианты"?!

Но мощу PostgreSQL ощутил, тем кто внутри оно может и не видно, а вот стороны — очень даже.

рост времени исполнения в зависимости от рамеров линейный

Зависимость времени выборки от размеров близка к линейной для BTree, но совсем не линейна для GIST.

Например, увеличим количество основных средств в 100 раз и сформируем таблицу не из 1.3 миллиона, а из 127 миллионов записей. Теперь так же выберем записи для каждого третьего основного средства на 1 января 2020 года, получив вместо 333 уже 33333 записей в выборке.

С использованием btree_gist:

Nested Loop  (cost=0.42..86956.11 rows=29517 width=22) (actual time=2.204..1495.583 rows=33333.00 loops=1)
  Buffers: shared hit=195120
  ->  Function Scan on generate_series g  (cost=0.00..333.33 rows=33333 width=4) (actual time=2.128..4.126 rows=33333.00 loops=1)
  ->  Index Scan using tmp_asset_location_asset_period on tmp_asset_location a  (cost=0.42..2.59 rows=1 width=22) (actual time=0.040..0.044 rows=1.00 loops=33333)
        Index Cond: ((asset_id = g.n) AND (date_period @> '2020-01-01'::date))
        Index Searches: 33333
        Buffers: shared hit=195120
Planning Time: 0.149 ms
Execution Time: 1497.564 ms

И с использованием только BTree:

Nested Loop  (cost=0.57..57672.79 rows=33333 width=22) (actual time=2.166..294.004 rows=33333.00 loops=1)
  Buffers: shared hit=166665
  ->  Function Scan on generate_series g  (cost=0.00..333.33 rows=33333 width=4) (actual time=2.119..3.976 rows=33333.00 loops=1)
  ->  Subquery Scan on a  (cost=0.57..1.71 rows=1 width=22) (actual time=0.008..0.008 rows=1.00 loops=33333)
        Filter: (upper(a.date_period) > '2020-01-01'::date)
        Buffers: shared hit=166665
        ->  Limit  (cost=0.57..1.70 rows=1 width=26) (actual time=0.008..0.008 rows=1.00 loops=33333)
              Buffers: shared hit=166665
              ->  Index Scan Backward using tmp_asset_location_asset_date_period on tmp_asset_location t  (cost=0.57..476.46 rows=422 width=26) (actual time=0.008..0.008 rows=1.00 loops=33333)
                    Index Cond: ((asset_id = g.n) AND (lower(date_period) <= '2020-01-01'::date))
                    Index Searches: 33333
                    Buffers: shared hit=166665
Planning Time: 0.135 ms
Execution Time: 296.121 ms

Как видно, при увеличении объемов данных в 100 раз, BTree действительно показал почти линейный прирост (296 мс против 3 мс), тогда как btree_gist заметно ухудшил свои показатели, проиграв уже в пять раз.

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

В общем то классика уже - стоимость выполнения отдельного запроса это не производительность СУБД в целом.

Снижение стоимости выполнения запроса не является необходимым и достаточным условием повышения производительности СУБД в целом.

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

Но в целом , спасибо за информацию и очередную тему для экспериментов 👍🤝.

На сгенерировавшихся у меня данных модель запроса из статьи - 4.8ms:

Nested Loop  (cost=0.43..1471.40 rows=333 width=26) (actual time=0.065..4.819 rows=333 loops=1)
  ->  Function Scan on generate_series g  (cost=0.00..3.33 rows=333 width=4) (actual time=0.028..0.066 rows=333 loops=1)
  ->  Subquery Scan on a  (cost=0.43..4.40 rows=1 width=26) (actual time=0.014..0.014 rows=1 loops=333)
        Filter: (upper(a.date_period) > '2020-01-01'::date)
        ->  Limit  (cost=0.43..4.38 rows=1 width=30) (actual time=0.013..0.013 rows=1 loops=333)
              ->  Index Scan Backward using tmp_asset_location_asset_date_period on tmp_asset_location t  (cost=0.43..1669.91 rows=422 width=30) (actual time=0.013..0.013 rows=1 loops=333)
                    Index Cond: ((asset_id = g.n) AND (lower(date_period) <= '2020-01-01'::date))
Planning Time: 0.223 ms
Execution Time: 4.880 ms

Теперь внимательно смотрим на ограничения генерации, что интервал у нас заведомо не превышает 15 дней. Исходя из этого запрос можно эффективнее переписать через поиск "от дат" вместо "от объекта" - 2.7ms:

EXPLAIN ANALYZE
SELECT DISTINCT ON(asset_id) -- могло попасть несколько интервалов объекта в эти 15 дней
  *
FROM
  tmp_asset_location
WHERE
  lower(date_period) BETWEEN '2020-01-01'::date - 15 AND '2020-01-01'::date AND
  asset_id IN (SELECT generate_series(3,1000,3))
ORDER BY
  asset_id, lower(date_period) DESC
Unique  (cost=5542.70..5553.25 rows=998 width=30) (actual time=2.499..2.602 rows=333 loops=1)
  ->  Sort  (cost=5542.70..5547.97 rows=2109 width=30) (actual time=2.498..2.528 rows=712 loops=1)
        Sort Key: tmp_asset_location.asset_id, (lower(tmp_asset_location.date_period)) DESC
        Sort Method: quicksort  Memory: 63kB
        ->  Nested Loop  (cost=2.94..5426.26 rows=2109 width=30) (actual time=0.127..2.240 rows=712 loops=1)
              ->  HashAggregate  (cost=2.52..4.52 rows=200 width=4) (actual time=0.097..0.165 rows=333 loops=1)
                    Group Key: generate_series(3, 1000, 3)
                    Batches: 1  Memory Usage: 61kB
                    ->  ProjectSet  (cost=0.00..1.68 rows=333 width=4) (actual time=0.003..0.025 rows=333 loops=1)
                          ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
              ->  Index Scan using tmp_asset_location_asset_date_period on tmp_asset_location  (cost=0.43..27.02 rows=6 width=26) (actual time=0.004..0.005 rows=2 loops=333)
                    Index Cond: ((asset_id = (generate_series(3, 1000, 3))) AND (lower(date_period) >= '2019-12-17'::date) AND (lower(date_period) <= '2020-01-01'::date))
Planning Time: 0.346 ms
Execution Time: 2.686 ms

Теперь подложим под него же чуть более подходящий индекс - и уже 2.1ms:

DROP INDEX tmp_asset_location_asset_date_period;
CREATE UNIQUE INDEX tmp_asset_location_lower_asset ON tmp_asset_location(lower(date_period), asset_id);
Unique  (cost=9236.34..9246.89 rows=998 width=30) (actual time=1.879..1.988 rows=333 loops=1)
  ->  Sort  (cost=9236.34..9241.61 rows=2109 width=30) (actual time=1.878..1.913 rows=712 loops=1)
        Sort Key: tmp_asset_location.asset_id, (lower(tmp_asset_location.date_period)) DESC
        Sort Method: quicksort  Memory: 63kB
        ->  Hash Join  (cost=144.35..9119.90 rows=2109 width=30) (actual time=0.446..1.632 rows=712 loops=1)
              Hash Cond: (tmp_asset_location.asset_id = (generate_series(3, 1000, 3)))
              ->  Bitmap Heap Scan on tmp_asset_location  (cost=137.33..9067.53 rows=6332 width=26) (actual time=0.282..1.062 rows=2119 loops=1)
                    Recheck Cond: ((lower(date_period) >= '2019-12-17'::date) AND (lower(date_period) <= '2020-01-01'::date))
                    Heap Blocks: exact=523
                    ->  Bitmap Index Scan on tmp_asset_location_lower_asset  (cost=0.00..135.75 rows=6332 width=0) (actual time=0.222..0.223 rows=2119 loops=1)
                          Index Cond: ((lower(date_period) >= '2019-12-17'::date) AND (lower(date_period) <= '2020-01-01'::date))
              ->  Hash  (cost=4.52..4.52 rows=200 width=4) (actual time=0.155..0.156 rows=333 loops=1)
                    Buckets: 1024  Batches: 1  Memory Usage: 20kB
                    ->  HashAggregate  (cost=2.52..4.52 rows=200 width=4) (actual time=0.088..0.122 rows=333 loops=1)
                          Group Key: generate_series(3, 1000, 3)
                          Batches: 1  Memory Usage: 61kB
                          ->  ProjectSet  (cost=0.00..1.68 rows=333 width=4) (actual time=0.003..0.023 rows=333 loops=1)
                                ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.000..0.001 rows=1 loops=1)
Planning Time: 0.309 ms
Execution Time: 2.082 ms

А если теперь перебрать все подходящие даты "вручную" и поискать в каждой, то становится уже 0.6ms:

EXPLAIN ANALYZE
SELECT DISTINCT ON(asset_id)
  *
FROM
  generate_series('2020-01-01'::date - 15, '2020-01-01'::date, '1 day') dt
, tmp_asset_location
WHERE
  lower(date_period) = dt::date AND -- точечно ищем подходящие объекты в каждой из дат
  asset_id IN (SELECT generate_series(3,1000,3))
ORDER BY
  asset_id, lower(date_period) DESC
Unique  (cost=464862.49..475405.50 rows=1000 width=38) (actual time=644.209..644.323 rows=333 loops=1)
  ->  Sort  (cost=464862.49..470133.99 rows=2108601 width=38) (actual time=644.208..644.243 rows=712 loops=1)
        Sort Key: tmp_asset_location.asset_id, (lower(tmp_asset_location.date_period)) DESC
        Sort Method: quicksort  Memory: 69kB
        ->  Merge Join  (cost=79550.77..128058.57 rows=2108601 width=38) (actual time=643.241..643.933 rows=712 loops=1)
              Merge Cond: (((dt.dt)::date) = (lower(tmp_asset_location.date_period)))
              ->  Sort  (cost=59.84..62.34 rows=1000 width=8) (actual time=6.051..6.054 rows=16 loops=1)
                    Sort Key: ((dt.dt)::date)
                    Sort Method: quicksort  Memory: 25kB
                    ->  Function Scan on generate_series dt  (cost=0.01..10.01 rows=1000 width=8) (actual time=5.964..5.969 rows=16 loops=1)
              ->  Materialize  (cost=79490.93..81599.53 rows=421720 width=26) (actual time=510.997..602.200 rows=324725 loops=1)
                    ->  Sort  (cost=79490.93..80545.23 rows=421720 width=26) (actual time=510.992..564.428 rows=324725 loops=1)
                          Sort Key: (lower(tmp_asset_location.date_period))
                          Sort Method: external merge  Disk: 17344kB
                          ->  Hash Join  (cost=7.02..29999.29 rows=421720 width=26) (actual time=0.305..326.025 rows=421957 loops=1)
                                Hash Cond: (tmp_asset_location.asset_id = (generate_series(3, 1000, 3)))
                                ->  Seq Scan on tmp_asset_location  (cost=0.00..21976.27 rows=1266427 width=26) (actual time=0.029..102.729 rows=1266427 loops=1)
                                ->  Hash  (cost=4.52..4.52 rows=200 width=4) (actual time=0.167..0.168 rows=333 loops=1)
                                      Buckets: 1024  Batches: 1  Memory Usage: 20kB
                                      ->  HashAggregate  (cost=2.52..4.52 rows=200 width=4) (actual time=0.099..0.133 rows=333 loops=1)
                                            Group Key: generate_series(3, 1000, 3)
                                            Batches: 1  Memory Usage: 61kB
                                            ->  ProjectSet  (cost=0.00..1.68 rows=333 width=4) (actual time=0.003..0.021 rows=333 loops=1)
                                                  ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
Planning Time: 0.543 ms
Execution Time: 656.556 ms

интервал у нас заведомо не превышает 15 дней

Этого не было в постановке задачи, поэтому опираться на это только на основании предельно упрощённого примера недопустимо.

Ну и к теме статьи Ваш комментарий имеет весьма косвенное отношение, так как GIST он вообще не затрагивает.

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

Как в статье - почему мы хотим отобрать аж целую треть объектов? Это достаточно много, и есть риск вообще попадать на Seq Scan, пока по каждому из них не накопится достаточно записей.

Мы делали аналогичную задачу по отпускам сотрудников (понятно, что они ровно так же не могут пересекаться) - так вот наиболее эффективным вариантом оказался тоже не btree_gist, а перебор "от длин отпуска" - просто потому, что их очень небольшое количество "в жизни". Отпуск обычно берут на 1, 2 дня, на неделю, на 2 недели или декретный (реальных вариантов побольше, но не сильно).

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

Еще раз, недопустимо опираться на особенности тестовых данных, которые не заявлены в постановке задачи. Это безответственно и опасно.

В данном случае, никто не давал гарантий, что нет основных средств, которые годами находятся на одной и той же площадке. Так же как никто не давал гарантий, что основных средств будет всегда 1000, а не миллион.

Как в статье - почему мы хотим отобрать аж целую треть объектов?

Потому что таковы бизнес требования.

Если интересно, то пример был взят максимальным упрощением реальной задачи. Основные средства - это вагоны, которых в конкретный момент под управлением может быть сотни тысяч. Суммарно же было под управлением за последние 20 лет - около миллиона. Под Дебальцево несколько составов стоят уже с 2014 года. А некоторые станции находятся настолько близко, что дискретизация границ периодов используется до минут, а не дней.

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

Так вот потому и отлаживать надо на данных, структурно приближенных к реальным. На относительно мелкой модели btree может выиграть, а на крупной - может и нет.

Основные средства - это вагоны, которых в конкретный момент под управлением может быть сотни тысяч. Суммарно же было под управлением за последние 20 лет - около миллиона.

Я не очень хорошо знаю примерный срок жизни вагона, но 100K/1M - это ни разу не "каждый третий". А когда еще лет через 5 окажется, что "в моменте" останется 75K/2M модель "от вагонов" может оказаться уже совсем неподходящей.

'2020-01-01'::date - 15

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

Предлагаю на это закончить.

P.S. Немного о математике и семантике. "Сотни тысяч" - это может быть и 300 тысяч. "Около миллиона" - это может быть и 900 тыс. Дальше посчитаете?

Срок службы полувагонов в России составляет 20-30 лет. При этом следует учитывать, что в собственности у крупного оператора находится несколько десятков тысяч вагонов. Остальные вагоны под управлением - это вагоны сторонних операторов в краткосрочной аренде или по доверенности. Просто по той причине, что на станции погрузки может не быть собственных вагонов, а гнать туда порожняк может оказаться невыгодным. Отсюда и возникает большая текучка вагонов под управлением в конкретный момент времени.

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

Скорее, стоит задуматься о судьбе разработчика (если мы говорим не просто о кодерах, а о грамотных специалистах уровня сеньора), который не уточнит эти граничные условия на основе имеющихся у него данных.

Вот мы смоделировали, уточнили применимость - можно рассматривать альтернативные варианты.

который не уточнит эти граничные условия на основе имеющихся у него данных

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

[Безотносительно конкретной задачи]

Вот если бы я представил реализацию без описания "почему использованы такие ограничения" - тогда это неправомерное решение.

А сформулированная явно гипотеза, заведомо корректная относительно имеющихся данных, - имеет право на жизнь и анализ более тесно интегрированным в предметную область специалистом.

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

Можете оставаться при своей точке зрения. Это исключительно Ваши проблемы. В моей же сфере деятельности категорически недопустимо накладывать какие-то ограничения, непосредственно влияющие на результат выборки данных, если эти ограничения не были предварительно согласованы с заказчиком и документально зафиксированы.

на крупной - может и нет

Я сократил модель, так как построение btree_gist индекса на реальных объемах данных может занять часы. Но при увеличении объемов данных GIST будет ещё больше проигрывать, в чём можете убедиться.

Sign up to leave a comment.

Articles