На пути к функциональной СУБД и NoSQL ERP: хранение остатков и расчет себестоимости

    Привет, Хабр!

    Продолжаем исследовать применимость принципов функционального программирования при проектировании ERP. В предыдущей статье мы рассказали зачем это нужно, заложили основы архитектуры, и продемонстрировали построение простых сверток на примере оборотной ведомости. По сути, предлагается подход event sourcing, но за счет разделения БД на иммутабельную и мутабельную часть, мы получаем в одной системе комбинацию преимуществ map / reduce-хранилища и in-memory СУБД, что решает как проблему производительности, так и проблему масштабируемости. В этой статье я расскажу (и покажу прототип на TypeScript и рантайме Deno), как в такой системе хранить регистры мгновенных остатков и рассчитывать себестоимость. Для тех, кто не читал 1-ю статью — краткое резюме:

    1. Журнал документов. ERP, построенная на базе РСУБД представляет собой огромный мутабельный стейт с конкурентным доступом, поэтому не масштабируется, слабо-аудируема, и ненадежна в эксплуатации (допускает рассогласование данных). В функциональной ERP все данные организованы в виде хронологически-упорядоченного журнала иммутабельных первичных документов, и в ней нет ничего кроме этих документов. Связи разрешаются от новых документов к старым по полному ID (и никогда наоборот), а все остальные данные (остатки, регистры, сопоставления) являются вычисляемыми свертками, то есть кэшируемыми результами работы чистых функций на потоке документов. Отсутствие стейта + аудируемость функций дает нам повышенную надежность (блокчейн на эту схему прекрасно ложится), а бонусом мы получаем упрощение схемы хранения + адаптивный кэш вместо жесткого (организованного на базе таблиц).

    Так выглядит фрагмент данных в нашей ERP
    // справочник контрагентов
    {
    	"type": "person", // тип документа, определяет режим кэширования и триггеры
    	"key": "person.0", // уникальный ключ документа
    	"id": "person.0^1580006048190", // ключ + таймштамп формируют уникальный ID
    	"erp_type": "person.retail",
    	"name": "Рога и копыта ООО"
    }
    // документ "покупка"
    {
    	"type": "purch",
    	"key": "purch.XXX",
    	"id": "purch.XXX^1580006158787",
    	"date": "2020-01-21",
    	"person": "person.0^1580006048190", // ссылка на поставщика
    	"stock": "stock.0^1580006048190", // ссылка на склад
    	"lines": [
    		{
    			"nomen": "nomen.0^1580006048190", // ссылка на номенклатуру
    			"qty": 10000,
    			"price": 116.62545127448834
    		}
    	]
    }
    

    2. Иммутабельность и мутабельность. Журнал документов делится на 2 неравные части:

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

    3. Свертки. Ввиду отсутствия семантики JOIN — язык SQL непригоден, и все алгоритмы пишутся в функциональном стиле filter / reduce, также имеются триггеры (обработчики событий) на отдельные типы документов. Вычисление filter / reduce назовем сверткой. Алгоритм свертки для прикладного разработчика выглядит как полный проход по журналу документов, однако ядро при исполнении делает оптимизацию — промежуточный результат, вычисленный по иммутабельной части, берется из кэша, а затем «досчитывается» по мутабельной части. Таким образом, начиная со второго запуска — свертка вычисляется целиком в оперативной памяти, что занимает доли секунд на миллионе документов (мы это покажем на примерах). Свертка досчитывается при каждом вызове, так как отследить все изменения в мутабельных документах (императивно-реактивный подход) очень сложно, а вычисления в оперативной памяти дешевы, и пользовательский код при таком подходе сильно упрощается. Свертка может использовать результаты других сверток, извлечение документов по ID, и поиск документов в топ-кэше по ключу.

    4. Версионность документов и кэширование. Каждый документ имеет уникальный ключ и уникальный ID (ключ + таймштамп). Документы с одинаковым ключом организованы в группу, последняя запись которой является текущей (актуальной), а остальные — историческими.

    Кэшем называется все, что может быть удалено, и снова восстановлено из журнала документов при старте БД. Наша система имеет 3 кэша:

    • Кэш документов с доступом по ID. Обычно это справочники и условно-постоянные документы, например журналы норм расходов. Признак кэширования (да/нет) привязан к типу документа, кэш инициализируется при первом старте БД и далее поддерживается ядром.
    • Топ-кэш документов с доступом по ключу. Хранит последние версии записей справочников и мгновенных регистров (например остатки и балансы). Признак необходимости топ-кэширования привязан к типу документа, топ-кэш обновляется ядром при создании / изменении любого документа.
    • Кэш сверток, вычисленных по иммутабельной части БД представляет собой коллекцию пар ключ / значение. Ключ свертки — это строковое представление кода алгоритма + сериализованное начальное значение аккумулятора (в котором передаются входные параметры расчета), а результат свертки — сериализованное конечное значение аккумулятора (может быть сложным объектом или коллекцией).

    Хранение остатков (балансов)


    Переходим собственно к теме статьи — хранение остатков. Первое что приходит в голову — реализовать остаток как свертку, входным параметром которой будет комбинация аналитик (например номенклатура + склад + партия). Однако в ERP нам нужно считать себестоимость, для чего необходимо сопоставлять расходы с остатками (алгоритмы ФИФО, партионный ФИФО, среднее по складу — теоретически мы можем усреднять себестоимость по любой комбинации аналитик). Другими словами, остаток нам нужен как самостоятельная сущность, а поскольку в нашей системе все является документом — остаток это тоже документ со специальным типом «баланс».

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

    В балансе хранится количество в единицах хранения и сумма в основной валюте, а разделив второе на первое — мы получаем мгновенную себестоимость на пересечении аналитик. Таким образом, в системе хранится не только полная история остатков, но и полная история себестоимостей, что является плюсом для аудируемости результатов. Баланс легковесен, максимальное количество балансов равно количеству строк документов (реально меньше, если строки группируются по комбинациям аналитик), количество топ-записей баланса не зависит от объема БД, и определяется количеством комбинаций аналитик, участвующих в контроле остатков и расчете себестоимости, таким образом размер нашего топ-кэша всегда прогнозируем.

    Разноска расходных документов


    Изначально балансы формируются приходными документами типа «покупка» и корректируются любыми расходными документами. К примеру, триггер документа «продажа» делает следующее:

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

    Пример изменения баланса при продаже

    // предыдущая запись баланса
    {
    	"type": "bal",
    	"key": "bal|nomen.0|stock.0",
    	"id": "bal|nomen.0|stock.0^1580006158787",
    	"qty": 11209, // количество
    	"val": 1392411.5073958784 // сумма
    }
    // документ "продажа"
    {
    	"type": "sale",
    	"key": "sale.XXX",
    	"id": "sale.XXX^1580006184280",
    	"date": "2020-01-21",
    	"person": "person.0^1580006048190",
    	"stock": "stock.0^1580006048190",
    	"lines": [
    		{
    			"nomen": "nomen.0^1580006048190",
    			"qty": 20,
    			"price": 295.5228788368553, // цена продажи
    			"cost": 124.22263425781769, // себестоимость
    			"from": "bal|nomen.0|stock.0^1580006158787" // баланс-источник
    		}
    	]
    }
    // новая запись баланса
    {
    	"type": "bal",
    	"key": "bal|nomen.0|stock.0",
    	"id": "bal|nomen.0|stock.0^1580006184281",
    	"qty": 11189,
    	"val": 1389927.054710722
    }
    

    Код класса-обработчика документа «продажа» на TypeScript

    import { Document, DocClass, IDBCore } from '../core/DBMeta.ts'
    
    export default class Sale extends DocClass {
        static before_add(doc: Document, db: IDBCore): [boolean, string?] {
            let err = ''
            doc.lines.forEach(line => {
                const key = 'bal' + '|' + db.key_from_id(line.nomen) + '|' + db.key_from_id(doc.stock)
                const bal = db.get_top(key, true) // true - запрет скана, ищем только в топ-кэше
                const bal_qty = bal?.qty ?? 0 // остаток количества
                const bal_val = bal?.val ?? 0 // остаток суммы
                if (bal_qty < line.qty) {
                    err += '\n"' + key + '": requested ' + line.qty + ' but balance is only ' + bal_qty
                } else {
                    line.cost = bal_val / bal_qty // себестоимость в момент списания
                    line.from = bal.id
                }
            })
            return  err !== '' ? [false, err] : [true,]
        }
    
        static after_add(doc: Document, db: IDBCore): void {
            doc.lines.forEach(line => {
                const key = 'bal' + '|' + db.key_from_id(line.nomen) + '|' + db.key_from_id(doc.stock)
                const bal = db.get_top(key, true)
                const bal_qty = bal?.qty ?? 0
                const bal_val = bal?.val ?? 0
                db.add_mut(
                    { 
                        type: 'bal', 
                        key: key,
                        qty: bal_qty - line.qty,
                        val: bal_val - line.cost * line.qty // cost вычислен в before_add()
                    }
                )
            })
        }
    }
    

    Конечно, можно было бы не хранить себестоимость прямо в расходных строках, а брать ее по ссылке из баланса, но дело в том, что балансы — это документы, их много, закэшировать все невозможно, а получать документ по ID чтением с диска — дорого (как индексировать последовательные файлы для быстрого доступа — расскажу в след. раз).

    Основная проблема, на которую указывали комментаторы — производителность системы, и у нас есть все, чтобы померить ее на относительно релевантных объемах данных.

    Генерация исходных данных


    Наша система будет состоять из 5000 контрагентов (поставщики и клиенты), 3000 номенклатур, 50 складов, и по 100k документов каждого вида — покупки, перемещения, продажи. Документы генерируются случайным образом, в среднем по 8.5 строк на документ. Cтроки покупок и продаж порождают по одной транзакции (и одному балансу), а строки перемещения по две, в результате 300k первичных документов порождают около 3.4 миллиона транзакций, что вполне соответствует месячным объемам провинциальной ERP. Мутабельную часть генерируем аналогично, только объемом в 10 раз меньше.

    Генерацию документов выполняем скриптом. Начнем с покупок, при проведении остальных документов триггер проверит остаток на пересечении номенклатуры и склада, и если хотя бы одна строка не проходит — скрипт будет пытаться cгенерировать новый документ. Балансы создаются автоматически, триггерами, максимальное количество комбинаций аналитик равно кол-во номенклатур * кол-во складов, т.е. 150k.

    Размер БД и кэшей


    После завершения скрипта мы увидим следующие метрики базы:

    • иммутабельная часть: 3.7kk документов (300k первичных, остальное балансы) — файл 770 Mb
    • мутабельная часть: 370k документов (30k первичных, остальное балансы) — файл 76 Mb
    • топ-кэш документов: 158k документов (справочники + текущий срез балансов) — файл 20 Mб
    • кэш документов: 8.8k документов (только справочники) — файл < 1 Mb

    Бенчмаркинг


    Инициализация базы. При отсутствии кэш-файлов, база при первом запуске осуществляет фуллскан:

    • иммутабельного дата-файла (заполнение кэшей для кэшируемых типов документов) — 55 сек
    • мутабельного дата-файла (загрузка данных целиком в память и обновление топ-кэша) — 6 сек

    Когда кэши существуют, подъем базы происходит быстрее:

    • мутабельный дата-файл — 6 сек
    • файл топ-кэша — 1.8 сек
    • остальные кэши — менее 1 сек

    Любая пользовательская свертка (возьмем для примера скрипт построения оборотной ведомости) при первом вызове запускает скан иммутабельного файла, а мутабельные данные сканируются уже в оперативной памяти:

    • иммутабельный дата-файл — 55 сек
    • мутабельный массив в памяти — 0.2 сек

    При последующих вызовах, при совпадении входных параметров — reduce() будет возвращать результат за 0.2 сек, при этом каждый раз выполняя шаги:

    • извлечение результата из reduce-кэша по ключу (с учетом параметров)
    • сканирование мутабельного массива в памяти (370k документов)
    • «досчет» результата путем применения алгоритма свертки к отфильтрованным документам (20k)

    Полученные результаты вполне привлекательны для таких объемов данных, моего одноядерного ноутбука, полного отсутствия какой-бы то ни было СУБД (не забываем, что это всего лишь прототип), и однопроходного алгоритма на языке TypeScript (который до сих пор считается несерьезным выбором для enterprise-backend приложений).

    Технические оптимизации


    Исследовав производительность кода, я обнаружил, что более 80% времени тратится на чтение файла и парсинг юникода, а именно File.read() и TextDecoder().decode(). К тому же высокоуровневый файловый интерфейс в Deno только асинхронный, а как я недавно выяснил, цена async / await для моей задачи слишком велика. Поэтому пришлось написать собственный синхронный ридер, и не особо заморачиваясь с оптимизациями, увеличить скорость чистого чтения в 3 раза, или, если считать вместе с парсингом JSON — в 2 раза, Заодно глобально избавился от асинхронщины. Возможно, этот кусок нужно переписать низкоуровнево (а может и весь проект). Запись данных на диск также неприемлемо медленная, хотя это менее критично для прототипа.

    Дальнейшие шаги


    1. Продемонстрировать реализацию следующих алгоритмов ERP в функциональном стиле:

    • управление резервами и открытыми потребностями
    • планирование логистических и производственных цепочек
    • расчет себестоимости в производстве с учетом накладных расходов

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

    3. Перевод FuncDB в многопользовательский режим. В соответствие с принципом CQRS — сканирование иммутабельных данных осуществляется т.н. «клиентскими» нодами, на которые копируются иммутабельные файлы БД (или шарятся по сети), а работа с текущими данными, кэшем, и транзакциями — осуществляется через выделенную «серверную» ноду. Это позволит масштабировать все тяжелые и медленные операции, а транзакционный сервер оставить легким, а значит — быстрым.

    4. Ускорение получения любого некэшированного документа по ID за счет индексирования последовательных файлов (что конечно нарушает нашу концепцию однопроходных алгоритмов, но наличие любой возможности всегда лучше чем ее отсутствие).

    Резюме


    Пока я не обнаружил ни одной причины отказаться от идеи функциональной СУБД / ERP, ведь несмотря на неуниверсальность такой СУБД, применительно конкретной задаче (учет и планирование) — мы имеем шанс получить многократное повышение масштабируемости, аудируемости и надежности целевой системы — и все благодаря соблюдению основных принципов ФП.

    Полный код проекта

    Если кто захочет поиграться самостоятельно:

    • установить Deno
    • склонировать репозитарий
    • запустить скрипт генерации базы с контролем остатков (generate_sample_database_with_balanses.ts)
    • запустить скрипты примеров 1..4, лежащих в корневой папке
    • придумать свой пример, закодить, протестировать, и дать мне обратную связь

    P.S.
    Консольный вывод расчитан на Linux, возможно под Windows esc-последовательности будут работать некорректно, но мне не на чем это проверить :)

    Спасибо за внимание.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 88

      +3
      Это все конечно классно, но вопрос, как вы собрались производительность без индексов обеспечивать? То есть нужно оборотную ведомость по одной группе товаров с даты по дату сформировать. SQL может построить план с пробегом по индексам, в том числе составным. А как вы это в NoSQL делать собрались?

      Ну и с ACID я так и не понял, что вы собираетесь делать. У SQL есть вполне декларативная и простая модель с блокировками (более) или update conflict'ами при RR/SERIALIZABLE уровнях изоляции. Как вы тот же остаток предлагаете целостным поддерживать?
        –3
        1) Индексы нужны для прямого доступа к данным любой глубины, отсюда и сбор статистики, и партиционирование. У нас нет такой необходимости — объем иммутабельных данных конечен, и цена перебора данных непосредственно в памяти соизмерима с поиском по нескольким индексам и поднятию блока из файла (как делают СУБД). Проблема, если мы захотим нестандартную свертку за старые периоды (например с 12 по 28 января прошлого года) — тогда это фуллскан. Хотя на самом деле нет, ведь в этом конкретном случае мы знаем диапазон timestamp, а значит можем прикинуть смещение в файле JSON, а далее seek() + read() за конечное время. Но это уже тема индексирования последовательного хранилища.

        2) Остатки не нужно блокировать, остаток при каждой операции генерируется новый. Блокировать нужно кэш на момент расчета этого остатка, самое простое решение — мьютекс на коллекцию, реально конечно придется заморочиться с уровнями изоляции, но сейчас у меня задача только прототипирование идеи, принципиальных проблем не вижу, если все данные в памяти, то наложить блокировку уж точно не сложнее, чем в РСУБД.
          +1
          1) Не совсем понял про цену? Каким образом цена перебора всех данных соизмерима с поиском по нескольким индексам? У них алгоритмическая сложность на порядок отличается (один — n, второе log(n)). Вы наверное забываете, что сейчас процессор узкое место (так как их производительность практически не растет), с памятью (со случайным доступом, как оперативной так и постоянной (SSD)) сейчас проблем нет, и можно хоть всю SQL СУБД в память положить. То есть закладываться на медленные последовательные HDD (и большой оверхед на долгое чтение блоков) в современном мире никакого смысла нет.

          Ну и не понял зачет этот огород со смещениями и seek и read, если SQL умеет это делать сам и из коробки.

          2) Ну то есть ручные пессимистичные блокировки. Опять-таки SQL это из коробки, а главное куда эффективнее за счет оптимистичных блокировок умеет делать.

          То есть я так и не понял чем вас SQL не устраивает?
            0
            Это мы уже в детали реализации погружаемся, я не спорю, придется решать все те же проблемы что решены в СУБД. А чем не устраивает SQL я уже ранее писал — мы не говорим о СУБД вообще, мы говорим о конкретном применении — учетная система. Ни одна из известных мне систем не масштабируется до уровня триллиона транзакций и петабайта данных. У них всех узкое место — блокировки, и неконтролируемое расползание ошибок пользовательских алгоритмов по сотне таблиц (из тысячи штатных). Изменить схему хранения данных, поле добавить — выгоняй пользователей на час, и т.д. Мне нужна биг-дата ERP, а там не пользуются ни блокировками ни транзакциями, и довольно редко — SQL.
              +4
              У них всех узкое место — блокировки

              Так их не от хорошей жизни делают. А именно потому что в ERP целостность данных действительно важна. Тут если остаток поплывет, это будет очень плохо (куда хуже чем незагруженная фотка в условном инстраграмме). И как раз версионные СУБД с их оптимистичными блокировками позволяют более менее эффективно решать проблему целостности, не сильно жертвуя масштабируемостью. То что многие учетные системы используют ручные пессимистичные блокировки это конкретно кривость рук их разработчиков, но вы то же предлагаете тоже самое.
              и неконтролируемое расползание ошибок пользовательских алгоритмов по сотне таблиц (из тысячи штатных)

              Это решается построением еще одного уровня абстракции над SQL. Но никак не переходом на NoSQL, где с этим может быть все еще хуже.
              Изменить схему хранения данных, поле добавить — выгоняй пользователей на час, и т.д.

              Вообще ЕМНИП СУБД позволяют очень много вещей делать не выгоняя пользователей. Не говоря уже о том, что динамическое изменение структуры по ходу и надежность — это противоположные вещи.
              Мне нужна биг-дата ERP

              Вообще у вас странная ERP. 24x7 (что уже редкость), с петабайтами данных и триллионами транзакций (что строго говоря для SQL на примитивной логике тоже не проблема, вспомните убер) и с логикой где не нужны выборки по условиям и целостность данных. Вы уверены что такие ERP существуют? И вообще в моем понимании ERP это именно что сложно-функциональные бизнес-приложения с высокими требованиями к целостности. А что это в вашем представлении?
                0
                Я с вами и не спорю. Статья не про то, что SQL плохо, а про схему разделения данных, и способ написания запросов. Проектик этот — чисто исследовательский, по результатам которого я кое-чему научился, и понял, что например могу миллиард записей обработать за минуты (а не часы), а на каком нибудь Rust и того быстрее, и что функциональные запросы не сложнее писать чем SQL, и так далее.

                По последнему вопросу — я достаточно с ERP поработал, и с нашими, и с ненашими, нет там высоких требований к плотности транзакций — максимум что я видел в штатном режиме работы — это одна транзакция в секунду :) Больше только в режиме закрытия периода, а это обычно по ночам. У биллингов и финтеха намного больше, вот там нужны вообще in-memory СУБД. А данных в ERP накапливается много, аудируемость важна, защита от злонамеренного разработчика или админа. Неспроста придумали и старательно форсят функциональное программирование — оно не про скорость, оно про надежность и тестируемость кода.
                  +2
                  Проектик этот — чисто исследовательский, по результатам которого я кое-чему научился, и понял, что например могу миллиард записей обработать за минуты (а не часы)

                  дурное дело нехитрое.
                  запустил select count(*) в ms sql с условием, не попадающим в индексы, на таблице с 200кк записей, в один поток — 24с. экстраполируем на миллиард записей — 2 минуты. если убрать option (maxdop 1) — будет в разы быстрее.

                    +1
                    А у вас эти 200кк записей не в памяти (в shared_buffers или как они там в ms sql называется)? Потому как если вся таблица в памяти, там цифры куда меньше будут.
                    +1
                    Неспроста придумали и старательно форсят функциональное программирование — оно не про скорость, оно про надежность и тестируемость кода.

                    А причем тут функциональное программирование? Это классная штука не спорю, но его можно одинаково «компилировать» как в SQL (что многие и делают) так и в NoSQL. И причин «компилировать» все в SQL куда больше, прежде всего из-за важных оптимизаций (с индексами и параллелизмом) и ACID из коробки.
                  +2
                  Ни одна из известных мне систем не масштабируется до уровня триллиона транзакций и петабайта данных

                  а ваша система с fullscan на каждый чих масштабируется?


                  них всех узкое место — блокировки, и неконтролируемое расползание ошибок пользовательских алгоритмов по сотне таблиц (из тысячи штатных)

                  из статьи:


                  К примеру, триггер документа «продажа» делает следующее:

                  как тут без блокировок/транзакций решается проблема с race condition при одновременном обновлении баланса двумя пользовтелями?

                    0
                    Системы бигдаты построены на последовательных файлах и map / reduce, и таки да, они масштабируются до размеров, где ни одна РСУБД не встанет. И там таки фуллскан, с параллельностью (не все алгоритмы можно распараллелить в принципе). И никто не говорил, что блокировки и транзакции не нужны, просто статья не про это немного, она больше ответ на вопросы к первой статье, и про функциональное программирование :)
            0
            -
              +3
              Почему это называется ЕРП?
                0

                Потому что это квинтэссенция 1С, JavaScript (простите, TypeScript) и Функционального Программирования. Иногда человек так упарывается в своих заблуждениях, что начинает считать труды поколений разрабов до него глупыми заблуждениями.

                  0
                  начинает считать труды поколений разрабов до него глупыми заблуждениями.
                  Вы что-то перепутали, я такого точно не говорил :)
                    +1
                    А вы на труды этих поколений, видимо, молитесь, не вдаваясь в суть) на каждый бэк накатываете постгрес+пгкластер+елк_стек (разумеется, всё в докере, который на виртуалке), а сам проект на фреймворках, которые всё через рефлекшины делают… да?)
                    Каждый инструмент хорош там, где он выполняет именно то ради чего создавался. И чем он менее универсален, тем лучше. А «умные дяди» работают за деньги и клепают что им скажут (а говорят, по моему опыту, клепать «универсальное» с кучей нахлабучек на каждый случай жизни сферического коня в вакууме), так что сколько бы сумоист не тренировался, а в паркуре лучшим не станет ;) ИМХО
                  +3
                  >> Почему это называется ЕРП?
                  Да вот то ж.
                  Крайне странные представления и о ERP, и о бигдате.
                  Какая-то исследовательская кривулька с фуллсканами, не тестированная даже на трех юзверях, вдруг стала продуктом уровня Enterprise (мы-то считали, что это когда хотя бы юзверей 100 стабильно годами работают в системе, и она не падает).
                  Вот что происходит, когда модные слова NoSQL, bigdata, функциональное программирование ложаться на неокрепшие мозги, и там в результате пермутаций рождается навязчивая идея, что все зло от SQL, хотя это наиболее эффективный инструмент для работы со структурированной информацией.

                  Хочется задать вопрос — а вот hadoop, spark, это у нас все же считается бигдатой, или только файлы с Json, которые парсятся TypeScript достойны носить это гордое имя?

                  Я к тому, что вот можно со скаловским DSL Quill написать строго в функциональном программировании (уж коли вам оно так нравится) с типобезопасностью и лямбдами что-то типа такого:

                  val q = quote {
                  for {
                  p < — query[Person]
                  a < — query[Address].join(a => a.fk == p.id)
                  c < — query[Company].leftJoin(c => c.zip == a.zip)
                  } yield (p,a,c)
                  }
                  ctx.run(q) //: List[(Person, Address, Option[Company])]
                  (отсюда github.com/getquill/quill)

                  А оно там само скомпилируется (о ужас!) в Spark SQL, выполнится на Hadoop кластере по простым паркетным файлам многотерабайтного объема, и никаких велосипедов изобретать не потребуется, достаточно просто выгрузить данные из обычной (настоящей) ERP в Hadoop.
                    0
                    Да, я в курсе про спарк, и вы таки правы.
                    PS
                    Хотя, на самом деле вы SQL на скале написали, семантика join, только со стрелочками. И никакой иммутабельности в помине.
                      +2

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


                      Но вы, очевидно, под этим термином подразумеваете запрет на изменение данных, что легко можно организовать даже в 1С: Бухгалтерия т.н. закрытием периода, и отьемом прав работы в закрытом периоде, отьемом прав на редактирование справочников. Хотя в случае с выгрузкой, ваши данные в hadoop и так никто не тронет.
                      Но уж точно эта проблема не решается переходом с SQL на NoSQL, в результате чего рождается "бигдата" из многократно задублированной при денормализации информации.
                      Разве что можно сьездить на конференцию и сказать "Мы работаем с ПЕТАБАЙТОМ!!!", но и то когда будете рассказывать подробности, конфуз может выйти. )

                    +1
                    Вы мегабайты с терабайтами или хотя бы гигабайтами не перепутали?
                    900 мегабайт данных должны работать за 0.1с на запрос на чайнике.
                      +1
                      Не совсем понял цифры у вас количество балансов больше чем собственно документов?
                      Преимущество реляционного решения — вообще можно обойтись без хранения промежуточных остатков. Их можно моментально получить полным пересчетом. По таблице с числовыми полями даже mysql справится.
                      Кроме того ваша система ляжет на сложных репортах, уже проходил это на одном из проектов на CouchDb.
                      Пришлось переносить все на mssql
                        0
                        Так в любой ERP количество промежуточных данных на порядок больше количества документов. В 1C вообще остатки по периодам хранятся, в Аксапте например каждая строка порождает как минимум складскую проводку, проводку ГК (иногда нет), сопоставление, cтроку потребности, и т.д. Без промежуточных данных контролировать остатки было бы нереально. А вот отчеты, согласен, дешевле из документов, потому что все равно пользователя интересует расшифровка любого агрегата до строки документа.
                          0
                          Так в любой ERP количество промежуточных данных на порядок больше количества документов

                          С промежуточными данными вы явно преувеличиваете. В 1С остатки хранятся для промежуточных периодов (по умолчанию по дням ЕМНИП, а потом по месяцам) и то только потому что а) они пытаются аналитику за далекие периоды в оперативную базу положить б) у них все очень плохо с оптимизацией запросов (в частности predicate push down'ами) и не храни они промежуточные итоги, с производительностью все было бы куда хуже. Так что это не показатель.

                          Остальные системы в основном хранят только актуальные остатки, а дальше обратным счетом рассчитывают показатели на нужные даты.
                            0
                            в одноце промежуточные данные исторически хранятся потому что очень медленное хранилище.
                              0
                              у них все очень плохо с оптимизацией запросов (в частности predicate push down'ами)
                              Кстати непонятно почему, ведь там mssql.
                                +1
                                Ну во-первых, у них кроме ms sql, еще файловая и postgresql есть. И даже если под ms sql нормально работает, а под postgresql и файловую ложится для них это не выход.

                                А, во-вторых, они весьма вольно обходятся с физмоделью / генерацией запросов (например в составных типах — эдаком суррогате наследования), вставляя туда OR'ы с CASE'ами, где даже MS SQL начинает конкретно чудить.
                                  0
                                  Одно время думал стать специалистом 1С но пообщавшись в кулуарах с топ-внедренцами вынес мнение (возможно и ошибочное), что 1C-ERP это лебединая песня, дальше они либо должны предложить что-то радикально новое, либо медленный закат. Аргументы — 1С упорно идет против тренда индустрии, продвигая свои уникальные регистры вместо обычных таблиц, бейсик вместо нормального ЯП, свой диалект SQL и т.д.
                                    +1
                                    Все не так просто. Бизнес-приложения это отдельный рынок, живущий по своим законам. 1С в нем наделал много ошибок, но остальные наделали их тоже достаточно, плюс рынок очень консервативный и инертный.

                                    Из тех кто удалось его хоть немного изменить, я знаю только Odoo — они как раз взяли что-то условно трендовое (python), навернули над ним свой фреймворк (на самом деле это самая важная часть, потому как синтаксис языка это дело второстепенное), сделали интерфейс более современным, ну и главное обеспечили хоть какую-то модульность и сделали маркетплэйс для разработчиков (аля отраслевок 1С). По итогу у них 16к модулей написанных сторонними разработчиками и уже 4 миллиона пользователей как у 1С. Но, надо понимать, что технологический потенциал Odoo ограничен, поэтому это строго говоря только малый бизнес (по западным меркам), ну и в принципе им даже на западе консолидировать рынок вряд ли удастся. Не говоря уже о том, чтобы сильно изменить уже частично консолидированный рынок в России (в тех сегментах куда они лезут).
                                      0
                                      Про рынок понятно, но интересна именно технологическая сторона. Питон начиная с 3.6 становится нормальным языком (я фанат статической типизации по количеству набитых шишек), и теперь Odoo становится интересным. Правда по одной вакансии на русскоязычную страну это так себе. Но у ADempiere и того нет. И почему технологические ограничения? Обычно так говорят, когда система не позволяет управлять сложностью. Вот Аксапта худо-бедно умеет — за счет наследования классов (теперь уже и таблиц), за счет слоев, и т.д. 1С не умеет, потому что модульность ограничена, наследования нет, язык примитивный. В питоне, теоретически, есть все средства для написания чего-то большого, я бы с удовольствием поработал на проекте Odoo, но ехать в Саратов пока не уверен что стоит :)
                                        +1
                                        Обычно так говорят, когда система не позволяет управлять сложностью.

                                        То что я видел, и по своему опыту с реально сложными логиками там будут проблемы, такие же как у условного 1С. А значит текущая их бизнес-модель на рынке среднего и крупного бизнеса не сработает. Модель 1С тоже не сработает, так как так консолидировать рынок они уже не смогут (рынок на западе уже в достаточно mature и конкурентном состоянии и нужно что-то более революционное).

                                        Ну и еще раз питон (как сам язык) это небольшая часть всей платформы (если мы говорим про платформы). Не надо переоценивать его роль. Есть еще штук 10 различных аспектов, где Odoo в чем-то лучше, в чем-то хуже. То есть может даже Odoo и лучше 1С, но не в 10 раз, а значит превратить brownfield в greenfield вряд ли ему удастся.

                                        PS: Например, если взять тоже наследование и полиморфизм в бизнес-приложениях ключевой момент там — поддержка полиморфизма в SQL слое, а это ни Odoo, ни 1С толком не удалось.
                                          0
                                          поддержка полиморфизма в SQL слое, а это ни Odoo, ни 1С толком не удалось.
                                          Уже 2 статьи написал на эту тему, но информация пока не заходит :) Если абстрагироваться от всяких мелочей моего pet-проекта, то останется главное — функционально-ориентированное прикладное API, которое, в отличие от повсеместно используемого процедурного с вкраплением SQL — более масштабируемо в плане управления сложностью, хотя и менее производительно, ведь за все надо платить.
                                            0
                                            Да не, все понятно. И функциональные подходы в разработке бизнес-приложений, действительно очень крутая штука, проверено на личном опыте. К вам то главный вопрос в другом. Зачем вы в NoSQL то все это компилируете. Почему не в SQL?

                                            Строго говоря разработчику все равно как это будет выполняться, если выполняться будет быстро и надежно. А у SQL все это есть из коробки. Собственно поэтому SQL по факту и стал стандартом в отрасли.
                                              0
                                              Для меня сейчас главное — понять насколько сложным и (не) понятным будет прикладной код для типовых алгоритмов типа расчета себестоимости в производстве или MRP, сколько промежуточных данных придется нагенерить, какие будут связи между этими данными, и т.д. Функциональщина предполагает работу с потоками данных, а SQL манипулирует стейтом, и это совершенно разные подходы.

                                              Все остальное просто лишняя работа, бинарная сериализация или компиляция в SQL — у меня нет свободных ресурсов чтобы это все писать, поэтому беру самое тупое и простое — цикл по файлу. В реальном продукте конечно это будет сделано по-другому (и не мной), наверняка все должно храниться в РСУБД с индексами, но это имеет смысл делать только тогда, когда имеет смысл делать.
                                                +1
                                                Для меня сейчас главное — понять насколько сложным и (не) понятным будет прикладной код для типовых алгоритмов типа расчета себестоимости в производстве или MRP, сколько промежуточных данных придется нагенерить, какие будут связи между этими данными, и т.д.

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

                                                В SQL как раз все четко разделено. За изменение стейта отвечает DML, за вычисление данных SELECT. И если таблицу рассматривать как поток данных на нее вполне отлично ложится функциональщина. Подходы как раз очень схожи. Посмотрите хотя бы на LINQ в .Net. Мы с lair много на эту тему спорили. Вот пример как это в .Net скажем сделано.
                                                  0
                                                  Эхх, сразу не понял кто вы :) Давно хотел почитать, но руки не доходили. Обещаю изучить и ответить по существу через день. Пока навскидку лишь могу сказать следующее — SQL предполагает random access к данным, то есть можно табличек накидать как попало, в любой запрос воткнуть join, и очень толстый и умный рантайм (СУБД) все оптимально выполнит. У меня была идея радикальней — чтобы получить результат на чистых итераторах, где нет RA, а максимум back(), нужно:
                                                  a) хранить данные специальным образом
                                                  б) придумать специальный «потоковый» DSL
                                                  в) реализовать продвинутый кэш
                                                  г) партиционирование потока + seek() в виде дополнительной плюшки
                                                  Честно говоря, понятия не имею, жизнеспособен такой подход или нет, наверняка исследования проводились, но я не нашел, на первый взгляд это дает возможность неограниченного масштабирования при сохранении рантайма простым. А значит диапазон применений — от аудита потока ЭСФ в масштабах страны, до рилтайм анализа сетевого трафика.
                                                  PS
                                                  Ушел читать про неочередной язык программирования…
                                                    0
                                                    Если говорить конкретно про масштабирование, то random access ему никак не мешает. Основная проблема масштабирования это ACID и теория распределенных транзакций. Та же Master-Slave репликация много где идет из коробки и позволяет достаточно эффективно решать проблему масштабируемости. Основной фокус в Master-Master. Некоторые СУБД пытаются решать эту проблему (например Оракл со своим RAC) но там не все так однозначно.

                                                    То что вы предлагаете это как раз увеличение производительности, за счет значительного упрощения и снижения оверхедов создаваемых SQL серверами (собственно это обычно и называют NoSQL). Но это подходит как раз для фейсбуков / инстаграммов с примитивной логикой, но очень большой нагрузкой. ERP это из другой оперы (я бы сказал противоположной). Вот Тут мы пытались все это немного систематизировать (насколько это в принципе возможно).
                                                      0
                                                      Если говорить конкретно про масштабирование, то random access ему никак не мешает.
                                                      Конечно мешает. Любой мьютекс на шаред-стейт приводит к ожиданиям потоков, а значит ограничивает параллелизм. Даже теорема есть специальная. Не от хорошей жизни придумали хаскель, где в принципе запрещен стейт, а производительность у него весьма впечатляет, по последним бенчам в блоге PsyHaSTe например.

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

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

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

                                                        Вот тут есть доля лукавства. Данные то он откуда то берет для вычислений, эти данные и есть state.

                                                        То есть если вам дать «фиксированную» SQL базу и политикой безопасности запретить DML (то есть разрешить только SELECT), вот вам и чистая функциональщина получится (тоже без state).
                                                        Опять же, если вы свои алгоритмы строите на итераторах без мутабельных таблиц — вам намного реже нужны транзакции, тем более распределенные, таким образом еще одно узкое место отпадает.

                                                        Но данные для вычислений откуда то должны браться.
                                                        PS
                                                        По предыдущей ссылке, респект, прочитал, но вы предлагаете языковое улучшение, а не смену парадигмы, поэтому ваш подход назвать функциональным можно с бОльшей натяжкой, чем мой.

                                                        А можно уточнить тогда что вы подразумеваете под функциональным подходом? Чистые функции? Но данные все равно должны как-то попадать в систему, для их использования. То есть если взять тот же SQL, то SELECT — чистые функции, DML — подносчик снарядов для этих чистых функций. Вы же кстати в курсе что SELECT полон по тьюрингу и на нем можно любые вычисления реализовать.

                                                        То есть по сути SQL (а точнее SELECT как его основной оператор) это и есть функциональный подход. И кстати причина почему он настолько популярен.
                                                          0
                                                          Я согласен, всякие ухищрения со слабыми мьютексами, lock-free каналами частично решают проблему масштабирования многопоточки, в той же лямбде предлагается по сути грязное чтение (устаревшие данные) для быстрой отдачи менее точного результата.

                                                          Под ФП я понимаю (это неканонично) прежде всего работу на потоках иммутабельных исходных данных с помощью ленивых итераторов и их композиции. В терминах ERP — вычисления на потоке первичных документов. Конечно, данные, то есть документы, где-то хранятся, но зная что они неизменные, мы можем много чего оптимизировать. В частности распараллелить свой алгоритм «ручками», а не надеяться на рантайм.

                                                          Конечно, в этом есть доля лукавства, ведь для любой свертки сложнее чем sum() нужен кэш ранее просмотренных документов (к которым вернуться уже невозможно), а кэш это и есть шаред стейт, со всеми вытекающими проблемами. Но пока я не вижу таких алгоритмов ERP, которые нельзя было бы реализовать таким способом. Пересопоставление в текущем периоде — да, это полное пересоздание мутабельной части журнала документов, то есть блокировка базы, что конечно хуже чем блокировка отдельных записей, но надежда в том что размер текущего периода — это миллионы транзакций, а не трилллионы, и вычисления в памяти переваривают это за секунды.
                                                            0
                                                            Ок, согласен, фокус в данном случае должен быть в ленивых вычислениях. Но их основная проблема в их обновлении при изменении данных от которых они зависят (инкрементальные обновления).

                                                            Собственно для этого в СУБД есть материализованные представления (они выполняют ту же функцию что и ленивые вычисления, с той лишь разницей что вычисляются сразу, а потом только инкрементально обновляются). Другое дело что в современных СУБД они реализованы мягко говоря в очень частных случаях. Но скажем в lsFusion нам удалось реализовать это в общем случае.
                                                            но надежда в том что размер текущего периода — это миллионы транзакций, а не трилллионы, и вычисления в памяти переваривают это за секунды.

                                                            Еще раз, то что вычисление идет в памяти не панацея. Современные СУБД спокойно забирают всю базу в память и тоже там выполняют все достаточно быстро. Проблема, когда одно и то же действие выполняется очень часто и сотнями разных пользователей. Как я уже писал узкое место в современных ИС не память, а процессор. И тут индексы единственное что может спасти.
                                                              0
                                                              основная проблема в их обновлении при изменении данных от которых они зависят (инкрементальные обновления).
                                                              Если алгоритм оформлен как reduce() то вообще не проблема — появились новые данные, скармливаем редьюсу, и он досчитывает свой аккумулятор. Проблема, если алгоритм не ложится на reduce(), а представляет, например, SQL — тогда инкрементальные обновления это сложно, и нужна СУБД. Самый типичный отчет — сводная таблица, если в агератах функции типа sum(), эта штука легко досчитывается и даже параллелится, а если там count_distinct(), то досчитывается, но не параллелится, и т.д.
                                                              индексы единственное что может спасти.
                                                              Собственно, да, у меня это кэш, кончится тем, что положу его в какой-нибудь Mongo, или даже Postgres. А вот исходные документы скорее останутся в бинарных файлах, так как СУБД отдают курсоры медленней чем файловая система, хотя вроде у оракла своя FS, не тестил.
                                                                0
                                                                Если алгоритм оформлен как reduce() то вообще не проблема — появились новые данные, скармливаем редьюсу, и он досчитывает свой аккумулятор. Проблема, если алгоритм не ложится на reduce(), а представляет, например, SQL — тогда инкрементальные обновления это сложно, и нужна СУБД. Самый типичный отчет — сводная таблица, если в агератах функции типа sum(), эта штука легко досчитывается и даже параллелится, а если там count_distinct(), то досчитывается, но не параллелится, и т.д.

                                                                Ну то есть только GROUP SUM по сути. А есть еще GROUP LAST, PARTITION, RECURSION, не говоря уже просто о композициях(JOIN) и арифметических/логических операциях. Задача на самом деле очень нетривиальная, прежде всего архитектурно. У Microsoft с Oracle ее решить не получилось. У нас же на нее лет 7 минимум ушло. А поверьте на одном GROUP SUM в ERP вы далеко не уедете.
                                                                А вот исходные документы скорее останутся в бинарных файлах, так как СУБД отдают курсоры медленней чем файловая система

                                                                Это экономия на спичках. Сейчас память настолько дешевая, что можно хоть всю базу в shared_buffers держать.
                                                                  –1
                                                                  Абсолютно верно, если данные лежат в стиле ООП — каждая сущность в отдельной таблице, связи p2p без ограничений, да еще по сложному ключу — нужен и сложный язык запросов. Преимущества — экономим место за счет нормализации. У меня идея ровно обратная — данные храним строго-упорядоченно, связи только назад и никогда вперед, любая коррекция это новый документ, и т.д. В этом случае нам нужны только простые свертки, оконные функции, и, возможно, вложенные reduce(). Приведите конкретный алгоритм ERP, который вы считаете сложным, я попробую его покрутить у себя.
                                                                  PS
                                                                  Вы в своем продукте замахнулись на гораздо большее — на универсальную СУБД, у меня задача проще — всего лишь ERP :)
                                                                    0
                                                                    Составьте расписание работ с учетом блокировки ресурсов и мощностей :)
                                                                      0
                                                                      Блокировка ресурса это надо понимать резервирование сырья? Про резервирование мощностей понятно, в простом случае когда производственные центры взаимозаменяемы, задача по крайней мере формализуется :)
                                                                        0
                                                                        ну, можно и так сказать
                                                                      0
                                                                      Преимущества — экономим место за счет нормализации

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

                                                                      Свертки это все хорошо, но данные «хранимые строго упорядоченно со связями» кто заполнять будет? По факту, вы получите 1С регистры, которые просто сумму по измерениям умеют считать, а как помещать данные в / обновлять эти самые регистры, как там делать ограничения и т.п. — «я стратег, а не тактик», а вы разработчики крутитесь как хотите.
                                                                        0
                                                                        Я не волшебник и не могу во все сразу. В проекте лежит скрипт обновления балансов, и триггеры на разноску покупок-продаж, это самое простое. Наверное, следующая статья (если будет) должна описывать управление резервами. А затем — планирование. Потому что с учетом в целом все понятно, не уверен что смогу решать системы линейных уравнений, но итеративно разрешить циклы думаю вполне реально.
                                                                          0
                                                                          В проекте лежит скрипт обновления балансов

                                                                          Ну то есть балансы вы предлагаете императивно обновлять, а не функционально. Как например тут: documentation.lsfusion.org/pages/viewpage.action?pageId=2228636
                                                                          Где вы в ФП задаете остаток, а дальше платформа все сама разруливает.

                                                                          То есть получается вы обеспечите функциональность в одном аспекте, а в остальных у вас будет такая же императивность как и везде.
                                                                            0
                                                                            receivedQuantity 'Суммарный приход' = GROUP SUM quantity(ReceiptDetail d) BY item(d), stock(receipt(d));
                                                                            shippedQuantity 'Суммарный расход' = GROUP SUM quantity(ShipmentDetail d) BY item(d), stock(shipment(d));
                                                                            currentBalance 'Текущий остаток' (Item i, Stock s) = receivedQuantity (i, s) (-) shippedQuantity (i, s);
                                                                            Вы что, каждый раз рассчитываете остаток от рождества христова? Не верю. Значит остаток таки храните, а функциональщину обеспечиваете магическим рантаймом. Я о таком тоже думал, но передумал, ибо у меня хранятся все балансы на любой момент времени, и смысл функционалить тогда.
                                                                              +1
                                                                              Есть магическая опция MATERIALIZED. Включаете ее и включается по сути ленивое вычисление. Само вычисление остатка при этом задается функционально. Вот в более классическом стиле с замыканием:

                                                                              receivedQuantity 'Суммарный приход' (Item i, Stock s) = GROUP SUM quantity(ReceiptDetail d) IF item(d) = i AND
                                                                              stock(receipt(d)) = s MATERIALIZED;

                                                                              А вы балансы императивно по сути заполняете. То есть получается «функциональщина» у вас в каких-то отдельных аспектах только есть.
                                                                                0
                                                                                Так и есть, потому что у меня балансы создаются каждый раз заново, и материализовывать просто нечего. Это не бага, а фича — история остатков / резервов / потребностей (это все разновидности балансов), прибитая гвоздями иммутабельности, весьма полезна в планировании покрытий и разруливании конфликтов.
                                                                                  0
                                                                                  Я не про то. Сама логика остатка (что это такое) у вас декларативно (функционально) или императивно задается? Можете ссылку на код, где задается что остаток это сумма приходов — сумма расходов сгруппированные по товару / складу?
                                                                                    0
                                                                                    Все императивно в триггере. Генерация нового баланса — Приход, Расход, Перемещение. Конечно, в проде нужно писать удобное API, но в прототипе, для целей демонстрации — самое то.
                                                                                      0
                                                                                      Ну так у вас ключевые агрегации императивно задаются :) Причем тут тогда функциональная СУБД в заголовке? NoSQL да, но в такой постановке 1С с их регистрами функциональнее :)
                                                                                        +1
                                                                                        ФП на уровне документов и бизнес-агрегатов, а в реализации конечно императивка. Если вы пишете например на Scala, у вас функция внешне чистая, но внутри нее вполне допустимы и мутабельные переменные, и циклы, мы же не параноики. В целом согласен, и заголовок и некоторые тезисы статьи немного провокационны, но не более чем у других :)
                                                                                          0
                                                                                          Ну провокации нужны, никто не спорит. Но не когда вы заявляете одно, а по факту получаете диаметрально противоположное :)

                                                                                          То есть у вас как раз «анти-функциональная» — императивная СУБД таким образом получится.
                                                                                0
                                                                                Я о таком тоже думал, но передумал, ибо у меня хранятся все балансы на любой момент времени

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

                                                                                  0
                                                                                  В иммутабельной части невозможно, а в мутабельной — при вставке задним числом — блокируется база, и последовательность документов пересоздается в памяти (удаляются старые баланcы начиная с точки изменения, новые пересоздаются по цепочке). Это если реальная правка задним числом (сторно). Если накладные расходы пришли в середине месяца — они просто дооценят текущие балансы, а старые останутся неизменными. То есть блокировка базы нужна будет далеко не всегда. На моем ноуте обработка миллиона документов в памяти — 0.6 секунды, на сервере время блокировки будет сотые. Конечно, если в вашей ERP открытый период — год, то моя схема вряд-ли подойдет :)
                                                                                    +1
                                                                                    1. "невозможно" — очень плохое слово, иногда нужно что-то менять в закрытом периоде;
                                                                                    2. мне кажется, что с ростом числа учитываемых величин, ваша система рухнет под хранением промежуточных значений, в той же 1с сделано более разумно — сущности вроде остатков хранятся с какой-то периодичностью, достигается компромисс между компактностью хранения и лёгкостью пересчёта с одной стороны, и простотой расчёта значения на произвольный момент с другой.

                                                                                    ну и сама идея "всё в плоском файле без индексов" мне не близка, поиск движений по какой-то гравицапе, которых было три штуки за всё время, превращается в fullscan, отчёт по складу — аналогично; и т.д., и т.п..

                                                                                      0
                                                                                      1) Теоретически можно менять и в закрытом, но это перезапись большого куска базы, долго и блокирующе. Издержи подхода.

                                                                                      2) Только практика ответит на этот вопрос, нужен пилотный проект и пилотный заказчик :) В свою защиту могу сказать, что 1С намного легче сломать или хакнуть, чем предлагаемую. В моей системе достаточно удалить все промежуточные данные (кэш), и истина будет восстановлена из первичных документов. Большие аудиторы, кстати, так и делают, и налоговая тоже.

                                                                                      3) Если аналгогичный запрос движений по гравицапе уже был — запись будет в кэше, и ее достаточно актуализировать на новых документах. Если нас интересует нестандартная свертка (какой еще не запрашивали), но без сальдо и по конкретному периоду (например обороты за прошлый год) — индексировать плоский файл по timestamp — плевое дело. Если фильтр не по времени — тогда извините, фуллскан.
                                                                                      PS
                                                                                      В целом я не против ни СУБД ни индексов, для прототипа выбрал файл только пожалев свое время, но все же хочется минимизировать мутабельность, то есть неконтролируемая правка любых записей в любой момент. В западных ERP к этому прикладывают специальные усилия, но их недостаточно.
                                                                                        +1

                                                                                        2 — в 1с есть "пересчёт регистров", именно про это самое

                                                                                          +1
                                                                                          Нет, скорее «перепроведение документов» про это самое. Пересчет регистров это все же немного из другой оперы (хотя наверное какая-то корреляция все же есть)
                                                                    +1
                                                                    Как я уже писал узкое место в современных ИС не память, а процессор
                                                                    Вы уверены..? Зачем тогда bson/varint и т.п.? Вроде как процессор как раз самое быстрое место компьютера, потом ОЗУ, за ним сеть и ПЗУ (они разные бывают, так что х.з. кого на первое место поставить)
                                                                    Плюс, такая тема как избыточное копирование данных между буферами (которым страдает Линукс и не страдает, вроде как, бсд)
                                                                  +1
                                                                  Вы же кстати в курсе что SELECT полон по тьюрингу и на нем можно любые вычисления реализовать.
                                                                  а «добавить» строки в возвращаемый результат — нельзя( в итоге либо встраиваемые процедуры писать (я не уверен, позволяют ли они это — решил вопрос вторым способом), либо на бэке это делать (в этом случае дополнительная задержка ввиду меньшей параллельности выполняемых задач)
                                                                    0
                                                                    Хранимка + временная таблица, теперь даже в 1С так делают :)
                                                                      0
                                                                      А что насчёт CTE? Они вроде как эффективнее временных таблиц, однако… я плохо знаю тему чтобы сравнивать дальше — поделитесь опытом (если имеете таковой), плиз (:
                                                                        0
                                                                        Так CTE оно для рекурсии придумано, произвольно обновить часть записей именованного курсора вы все равно не сможете. У меня давно еще была задача расчета плановой себестоимости по дереву BOM с учетом потерь и отходов на чистом SQL. Никакие синтаксические изыски этого тьюринг-полного языка не позволили избежать временных таблиц. Возможно лично я и ошибаюсь, но не от хорошей жизни процедурные расширения добвили во все СУБД.
                                                          0
                                                          Все это зависит от модели предметной области.
                                                          Допустим, если процесс организован как {Входы, Процессоры, Выходы},
                                                          то стоимость Процесса = Стоимость Входов + (Износ Процессоров + Расход ТМЦ)
                                                          а стоимость конкретного Выхода = Стоимость Процесса / (Количество Выхода * Коэффициент распределения)
                                                          Если предположить, что Потребность любого Процесса клиента удовлетворяется Процессов сервером, который поставляет некоторое количество своих Выходов во Входы Процесса клиента, то цепочка формирования стоимости становится тривиальной структурой и обрабатывается алгоритм в три строчки кода. При этом учитывая, что каждый Процесс запущен (или спланирован) в контексте спецификации производственного заказа, производственного заказа, заказа, контракта, то легко вычислить все соответствующие показатели, вплоть до перераспределения затрат и т.д…
                                                          Хотя все эти расчеты нафиг не уперлись, так как прибыль никак не зависит от наличия и/или точности этих расчетов, а цену определяет рынок и /или административный ресурс.
                                                          Это все к тому, что парадигмы программирования и т.д. белиберда к реальным задачам бизнеса отношения не имеет.
                                                            0
                                                            В целом соглашусь что бизнес-алгоритмы просты, но всеж бывают ньюансы. Например:
                                                            — Зацикливание графа сопоставлений при наличии возвратных отходов или реклассификации готового продукта в полуфабрикат.
                                                            — Дооценки сырья сильно задним числом (когда продукцию произвели и уже продали).
                                                            — Коррекция потребностей, уже принятых в работу отделом закупок (купили, а это никому не нужно).
                                                            — Коррекция планов производства внутри декады по причине форсмажора.
                                                            — Управление резервами по приоритетам (автоматически снять резерв с менее важного, и отдать более важному).
                                                            Это все может рекурсивно затрагивать старые данные, и наш принцип иммутабельность будет нарушаться. В классических ERP проще — я там могу в старых записях дозаполнить новые поля, а с журналом документов мне придется любую коррекцию чего-нибудь (да хоть резерва) оформлять отдельным документом. Я пока масштаб бедствия до конца не осознал, видимо нужно придумать какой-то радикально-сложный пример, может Andrew-BUSINESS имеет что-то готовое, либо придется самому ползти медленно и постепенно.
                                                              +1
                                                              balajahe

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

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

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

                                                              3. Дальше этот граф «раскручивается» — решается последовательность уравнений, в которых могут быть и циклы (системы уравнений).

                                                              Не знаю, это как-то поможет понять как решать те проблемы, которые Вы перечислили?
                                                                0
                                                                Исчерпывающе!
                                                                  0
                                                                  Остается смоделировать это на функциональной СУБД и понять, насколько она годится для этой задачи:)
                                                                    0
                                                                    Попробую, но все-таки наверное сначала резервы, потом планирование, и только потом учет. Учет штука более однозначная, в планировании же куча трудно-формализуемых вещей, на чем проекты ERP и ломаются. В планировании откат в прошлое это почти норма, а в учете это стихийное бедствие.
                                                                      0
                                                                      Планирование очень сложное, да.
                                                                +1
                                                                Отходы — обычные Выходы (обычно Процессы потребители этих выходов вне контекста Производства — это утилизация и т.д., хотя он могут быть и переработаны).
                                                                Все эти «классификации» аспектные, любой набор свойств может классифицирован множественно и одновременно и при том динамически. Так что проблем чего то переклассифицировать стоит в системах с единственной «верной» классификацией (типа буховской) на все времена и случаи.
                                                                ДоПереОценка и т.д. — виртуальные операции для некоторой отчетности, по сути нафи никому не нужные.
                                                                Отдел закупок не может закупать что благорассудится, они исполнители процессов (хранения, перемещения) сгенерированных системой планирования процессов.
                                                                Перераспределение резервов происходит автоматически по заданным Политикам потребления заделов системой планирования процессов.
                                                                Управление — реакция на изменения стейта :) происходит автоматически по заданным правилам и при нарушении определенных ограничений.
                                                                Все эти документы и т.д. — некое представление Процесса (т.е. структурно все это описано как Процесс), потому ничего отдельно оформлять не надо.
                                                                Ух ты, почти все раскритиковал, негоже это воще то.
                                                                Что бы не удариться во все тяжкое надо почитать про вещи попроще, типа TPL DataFlow. habr.com/ru/post/138531
                                          0
                                          в моей системе нет вообще никаких промежуточных данных и все нормально. Вы как то слишком все усложнили — проводка по складу это просто запись об изменении количества в ту или иную сторону. Каким боком там вообще всякие потребности и прочее.
                                          остутствие промежуточных остатков позволяет простым запросом высчитывать обороты и остатчки и не заглядывать на какую дату каки промежуточные там данные. представте что у вас промежуточный итог на первое января мне надо посмотреть отчет за период с середины декабря по середину января. Могу я просто послать к БД запрос? в вашем случае врядли.
                                          Кроме того это позволяет легко проводить документ=нты любым числом хоть задним хоть передним без пересчета остатков. прибил записи связаные с документом и добавил новые и все.
                                          Я к тому что при нынешней копеечной стоимости железа так усложнять хранилище это экономия на спичках. И никакие nosql пока не переплюнут реляционные БД для учетных систем где данные реляционные по своей природе.
                                            0
                                            Кроме того это позволяет легко проводить документ=нты любым числом хоть задним хоть передним без пересчета остатков. прибил записи связаные с документом и добавил новые и все.
                                            Нельзя этого делать. Как только вы добавите планирование, вы поймете почему. Потребности нельзя вычислять на лету, их нужно фиксировать с определенной периодичностью (например не чаще раза в месяц). Иначе ваш отдел закупок с ума сойдет от такого динамизма.
                                        +1

                                        Вы изобретаете каппа архитектуру. Рекомендую поликбезить сабж.

                                          0
                                          Респект, уже :)
                                          +1
                                          Супер!
                                          Спасибо, что оперативно выпустили эту статью, ответив таким образом на мой комментарий.

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

                                          Буду стараться следить дальше.
                                          Если вдруг нужна будет помощь по методологической части (резервы, себестоимость, цепи поставок) — можете обращаться.
                                            0
                                            Спасибо, обращусь за помощью как дойдем до планирования!
                                            Если раньше не брошу эту неприбыльную затею :)
                                              0
                                              У меня самого есть идея запилить пост о новой бизнес-логике для ERP.
                                              Возможно, я заставлю себя, чтобы руки дошли до него чуть быстрее.
                                            0
                                            NitroJunkie, по поводу вычисления остатков — отвечу развернуто. В ERP нам нужен граф сопоставлений — и для себестоимости, и для планирования. Есть 2 способа организации сопоставлений:
                                            1) проводка-проводка
                                            2) проводка-остаток
                                            Если мы выбираем первый случай, тогда мы можем расчет остатка оформить вычисляемой кэшируемой сверткой, в чисто-функциональном ключе. Я сознательно выбрал второй вариант «проводка — остаток», таким образом остаток нам нужен в системе как самостоятельная сущность, с уникальным ID. Какая может быть функциональщина при организации связей между сущностями?

                                            Любая функция (даже чистая) возвращает какие-то данные, в моем случае — новый документ типа «баланс», тогда как императивный подход подразумевает изменение старой записи регистра «баланс». Чувствуете разницу? Таким образом моя система намного ближе к принципам ФП, чем упомянутая 1С. Про вашу систему пока ничего определенного сказать не могу, решение с материализацией на лету — довольно сильное, но подходы к хранению данных принципиально другие, значит и никакого спора быть не может.
                                              0
                                              Если честно, для меня рассматривать остаток как сущность (класс, объект и т.п.) это очень странная концепция. На мой взгляд остаток это чистая функция.
                                              Любая функция (даже чистая) возвращает какие-то данные, в моем случае — новый документ типа «баланс».

                                              Вообще чистая функция (а ФП это именно про чистые функции, потому как не чистые функции это просто процедуры, которые умеют записывать результат в переменные) никак не может возвращать новый документ. Потому как при повторном вызове она получается вернет другой документ, что противоречит ее чистоте. Это никак не ФП.
                                              Чувствуете разницу?

                                              Нет, разница чисто в особенностях реализации. По сути вы просто создаете транзакционный лог. Я бы на вашем месте это скорее блокчейн СУБД, а не функциональной СУБД называл бы. Ведь именно блокчейн это про аудируемость и т.п.
                                                0
                                                Остаток как сущность — для меня самого это странно, но в любое странное место нужно хоть раз сходить. У меня предчуствие, что это даст упрощение алгоритмов MRP, ведь и резерв и непокрытую потребность можно представить как разные типы остатков, сейчас как раз пытаюсь что-то подобное смоделировать.

                                                Функциональная функция (простите за тафталогизм) всегда возврщает новые данные, поэтому ФП так критикуем и непопулярен, ведь аллокации памяти это дорого. А если фунция ничего не возвращает, и не меняет старых данных, то это не функция вообще, а черная дыра :)

                                                По сути вы просто создаете транзакционный лог. Я бы на вашем месте это скорее блокчейн СУБД, а не функциональной СУБД называл бы. Ведь именно блокчейн это про аудируемость и т.п.
                                                Именно! Вы совершенно верно схватили суть! На такую схему навернуть блокчейн — пять минут программирования, просто вычислить хэш и положить в дерево Меркла. А вот если у нас есть мутабельный регистр «Остатки», то как его надежно защитить от подделок и ошибок? Никак.
                                                  0
                                                  Функциональная функция (простите за тафталогизм) всегда возврщает новые данные, поэтому ФП так критикуем и непопулярен, ведь аллокации памяти это дорого.

                                                  С чего вы взяли? ФП вообще перпендикулярен работе с памятью (то есть можно по разному делать). И как раз в ФП экономить память (и ее аллокацию) куда проще чем в императивном: а) из-за ленивых вычислений, б) потому что вы можете переиспользовать ссылки на объекты не боясь что их кто-то изменит (если у вас вся архитектура на immutable объектах / чистых функциях, соответственно immutable объекты создаются только в императивной части — процедурах). И кстати поэтому ФП очень популярен именно в системном программировании, потому как на сложных архитектурах при использовании ФП с многопоточностью и памятью (кэшированием) все становится значительно проще.
                                                  Именно! Вы совершенно верно схватили суть! На такую схему навернуть блокчейн — пять минут программирования, просто вычислить хэш и положить в дерево Меркла. А вот если у нас есть мутабельный регистр «Остатки», то как его надежно защитить от подделок и ошибок? Никак.

                                                  Ну насколько хорош или плох блокчейн это отдельная тема. И находится она в основном в политическо/экономической плоскости, так что к данной статье имеет мало отношения :)
                                                    0
                                                    Ну не знаю. Бывает, что сама задача хорошо ложится на ФП, но чаще нужно специальным образом писать чтобы «переиспользовать ссылки на объекты не боясь что их кто-то изменит». В реальной жизни все хуже, я вот недавно делал бенчмарк, на котором Scala умерла, а комментаторы указали, что я выбрал неправильный алгоритм. То есть ФП работает только на правильных алгоритмах :) Я сколько не пробовал — суровая императивщина с указателями на кучу и циклами получалась быстрее всяких итераторов. А вот применительно к БД — подход уже интереснее, потому что в БД накладные расходы на изменение старой записи соизмеримы с накладными расходами на добавление новой. Другими словами, функциональщина в БД обходится дешевле чем в ОС.

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

                                            Самое читаемое