Как хранить и иметь быстрый доступ к данным?
Имеется простая структура списка операций, в которой ключ - порядковый номер операции, а значение - набор данных (на данном этапе несколько реализаций):
// Версия с объектом в значении
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, время: 14:35. |