Как хранить и иметь быстрый доступ к данным?
Имеется простая структура списка операций, в которой ключ - порядковый номер операции, а значение - набор данных (на данном этапе несколько реализаций):
// Версия с объектом в значении const operations_objects = new Map([ [0, { from: 'e3d08a24-971f-4e5b-b646-a9decd12f05d', // UUID от кого to: 'd82bbb1d-0ab0-4d4d-8f97-41ddf6460c41', // UUID кому value: '500000000', // сколько (может быть уникальным значением) и принципиально строковое значение BigInt total_from: '100000000', // сколько осталось у отправителя total_to: '500000000', // сколько стало у получателя time: 1673357366 // дата проведения операции в секундах (от 01.01.1970) }], [1, { from: 'bd166b6d-381d-4109-9b4b-7a48a26b4119', to: 'ef59025a-165d-407e-831b-ab3464ae3861', value: '300000000', total_from: '700000000', total_to: '400000000', time: 1673357377 }] ]); //ИЛИ // Версия со строкой в значении const operations_strings = new Map([ [ 0, 'e3d08a24-971f-4e5b-b646-a9decd12f05d d82bbb1d-0ab0-4d4d-8f97-41ddf6460c41 500000000 100000000 500000000 1673357366' ], [ 1, 'bd166b6d-381d-4109-9b4b-7a48a26b4119 ef59025a-165d-407e-831b-ab3464ae3861 300000000 700000000 400000000 1673357377' ] ]); 1. По итогу необходимо иметь мгновенный доступ к значениям value (типа есть ли такое значение у получателя операции) // Псевдопример: operations.has('uuid-8a24-971f', '234489274'); // есть ли у участника 'uuid-8a24-971f' операция, в которой параметр value равен '234489274' // ИЛИ // как вариант поиск значения вообще по всем операциям (может так проще будет..) // Псевдопример: operations.has('234489274'); // типа есть ли вообще операция, в которой параметр value равен '234.489274' 2. Получить значение total_from или total_to // Псевдопример: operations.total('uuid-9b4b-7a48'); // типа найдёт самую последнюю операцию, где есть 'uuid-9b4b-7a48', определит кто он — from или to и выведет total_from или total_to 3. Вывести список всех операций (или их часть) по конкретному участнику // Псевдопример: operations.get('uuid-9b4b-7a48', 100); // типа выведет последние 100 операций с участником 'uuid-9b4b-7a48' У меня была идея, что может следует каждый параметр операции вообще в отдельных Map хранить (по типу колонок) — но это актуально лишь для уникальных ключей. Например, если принудительно сделать параметр value UNIQUE — то, теоретически, можно создать Map, где ключом будет значение value, а значением — порядковый номер транзакции, типа // const values = new Map([ ['234434505', 0], // в ключе — значение value, а в значении — номер операции ['250400000', 1] // и т.д. ]); // при чем в таком случае из структуры operations можно вообще тогда убрать параметр value — потому что он будет вынесен в отдельную колонку // НО // по итогу при общем большом количестве операций думаю могут возникнуть трудности. и оптимальнее все таки искать value у конкретного участника: //operations.has('uuid-8a24-971f', '234489274'); // но это только предположение.. И вы можете спросить — а зачем хранить в операциях параметры total_from и total_to — и я отвечу: потому что логика максимально простая — мы можем только добавлять или читать данные (возможность обновления или удаления полностью отсутствует). :) |
Цитата:
Их все таки как числа или как строки сравнивать? |
Цитата:
Т.е. по итогу после точки у всех value будет одинаковое количество знаков (или как вариант — хранить без точки, а в целых значениях, но то же в строке - т.к. bigint). Поэтому думаю актуальнее сравнивать как строки?) |
Дробные нельзя в BigInt
А если без точки, то не понятно, где целая, где дробная, и как их складывать и вычитать Цитата:
'234.489274000000000' '234.489274' '23448.9274' ? |
Цитата:
|
Цитата:
|
Цитата:
'234.489274000000000' -> 234489274000000000 '234.489274' -> 234489274000000000 '23448.9274' -> 23448927400000000000 Т.е. в вашем случае было принято решение использовать 15 знаков после запятой — значит все числа в этой системе будут иметь 15 знаков после запятой. Это число знаков, очевидно, может быть выбрано только 1 раз. |
Цитата:
Итого строка длиной 34 знака. И если целое меньше до дополняется нулями слева до 17 знаков, а дробная часть дополняется нулями справа до 17 знаков Цитата:
'0000000000000023448927400000000000' и '0000000000002344892740000000000000' (Все равно не понятно, как их делить и умножать) |
Цитата:
Ну в общем, уже же решили, что сейчас все числа просто имеют тип BigInt, так что с дробными числами можно успокоиться)) |
Цитата:
|
Цитата:
|
Цитата:
// if('100' === '100') return true; // а если всё таки требуется математические сравнения больше-меньше, то тогда определенно надо привести к виду BigInt |
Цитата:
Из того, что написано может быть такая схема const operations_objects = [ { from: 'e3d08a24-971f-4e5b-b646-a9decd12f05d', // UUID от кого to: 'd82bbb1d-0ab0-4d4d-8f97-41ddf6460c41', // UUID кому value: '500.00000000000000', // сколько (может быть уникальным значением) и принципиально строковое значение total_from: '100.00000000000000', // сколько осталось у отправителя total_to: '500.00000000000000', // сколько стало у получателя time: 1673357366 // дата проведения операции в секундах (от 01.01.1970) }, { from: 'bd166b6d-381d-4109-9b4b-7a48a26b4119', to: 'ef59025a-165d-407e-831b-ab3464ae3861', value: '300.00000000000000', total_from: '700.00000000000000', total_to: '400.00000000000000', time: 1673357377 }, ]; memberOperations = new Map () Ключом является UUID участника Значение - такой объект { operations: [<n.op>, <n.op>, <n.op...] // номера операций м массиве operations_objects с этим участником values: new Map(); } ключ у values - values из операции значение - [<n.op>, <n.op>...] - номера операций м массиве operations_objects с этим участником и с этим values |
Цитата:
Операции добавлять с помощью unshift(). Массивы в данном случае действительно быстро будут работать? А то мы выяснили, что они ппц какие медленные. |
Цитата:
Цитата:
Из того. что вы написали в первом посте, я операций поиска не вижу. Только взять n последних для Цитата:
|
Цитата:
|
Цитата:
|
Вопрос, что чаще делается.
Если добавляем гораздо чаще, чем получаем последние n, то лучше push. А получить время от времени последние можно через slice(-n) |
Цитата:
"получить время от времени" — это о чем вообще?))) |
Цитата:
|
Цитата:
Если так, то для value (который внутри мэпа по участникам), ненужно другого мэпа с массивом, достаточно set, что бы проверить, есть ли операция с таким value у данного участника |
Цитата:
Если вдруг понадобится какой то поиск, то все равно перебор, что у массива, что у списка. |
Цитата:
|
Цитата:
(На порядок быстрее, чем всякие переводы из строк в BigInt) |
Цитата:
Ну а как вы предлагаете ещё хранить BigInt? Все эти данные необходимо ещё и экспортировать, импортировать. Конечно, при импорте актуально сразу в BigInt преобразовывать. Но в конечном-то счете это будет храниться в обычной строке. |
Цитата:
Тут список быстрее. Не надо ничего двигать, а только переписать 6 ссылок. splice двигает конечную часть массива, освобождая место и меняя все дальнейшие индексы У push просто запись в конец массива. Ничего двигать не надо. У shift надо подвинуть весь массив, переписать все индексы Вот сравните выполнение 100000 раз push и shift const NA = 100_000; let arr; let na = NA; arr = []; let s = 'aaaaaa'; console.time('push'); while (na--) arr.push(s); console.timeEnd('push'); na = NA; arr = []; console.time('shift'); while (na--) arr.unshift(s); console.timeEnd('shift'); |
Цитата:
|
Вставка (в смысле в середину) для списка быстрее. (ну для длинных массивов).
В конец дописать - массив однозначно быстрее. То же для удаления. Удалить последний (pop) массив быстрее. Если удалять из середины, то список быстрее |
Цитата:
|
Цитата:
Если скажем взять обычные целые. Максимальное целое число, которое может быть точно представлено в js - Number.MAX_SAFE_INTEGER = 2**53 - 1 = 9,007,199,254,740,991 16 разрядов однако. Может этого будет достаточно? |
Цитата:
|
Цитата:
class ItemList { next = null; prev = null; operation; constructor(operation) { this.operation = operation; } } class List { first = null; last = null; constructor(){} add(item) { this.first ??= item; item.prev = this.last; if(this.last) this.last.next = item; this.last = item; return this; } } const list = new List(); const arr = []; const NA = 1_000_000; let na; na = NA; console.time('list'); while(na--) list.add(new ItemList(na)); console.timeEnd('list'); na = NA; console.time('arr'); while(na--) arr.push(na); console.timeEnd('arr'); Насколько корректно проведено тестирование?) |
Цитата:
|
Цитата:
Если массив operations, то это зависит от ваших условий. Сколько там операций у вас идет? Про Map точно не знаю ограничений, но пару миллионов точно влезает. Где то читал про объекты, вроде нельзя больше 8 миллионов полей. Думаю для Map примерно такое же ограничение. Ну и разумеется, должно хватить памяти у компа. |
Цитата:
Вот колоночные как-то больше всего подходят (именно архитектура). Но по итогу ни одна из них не подходит. Понятное дело, что безграничные данные хранить в JS - тоже не выход, т.к. всё таки есть ограничения у JS движков. Но как вариант — хранить это в JS, а потом просто огромной пачкой сгружать на диск и хранить в виде столбцов. Либо, допустим, построчно хранить операции в файле — типа как лог. А отдельно ещё сделать в виде колонок — чтобы иметь мгновенный доступ к требуемым данным. Например, в одной колоночной БД — реализован механизм, который создаёт виртуальные колонки, отсортированные по конкретной колонке: Н-р, в нашем случае есть 3 основных столбца — from, to, amount. Если представить их в хронологическом порядке, то в столбце amount всё идет как попало и сложно что-либо найти быстро. Для этого создаётся отдельная колонка (или "представление") в котором все данные отсортированы по порядку — как результат мы можем использовать либо бинарный поиск, либо ваще интерполирующий (вот бы ещё понять как его на строки перевести). По итогу ограничение данных просто отсутствует. |
Тогда только думать про БД. С индексами и все как положено.
Рассмотрите Mongo, как вариант. Но с любой БД вы не получите таких скоростей, как с хранением в оперативной памяти Все таки хранение на диске, считывание индексов, самих данных.... О временах в пределах нескольких мс на операцию можно не мечтать. |
Цитата:
Хм, а если тогда использовать несколько серверов? Типа в тех же колоночных БД данные хранятся в файлах размером до 256 МБ. Получается вместо файла просто будет отдельный веб-сервер. :D |
Цитата:
|
Цитата:
или как-то может виртуальных серверов? |
Не знаю. Совсем не спец по архитектуре серверов. В любом случае надо будет смотреть, что там с временами запросов будет.
|
Часовой пояс GMT +3, время: 08:05. |