Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Как хранить и иметь быстрый доступ к данным? (https://javascript.ru/forum/misc/84837-kak-khranit-i-imet-bystryjj-dostup-k-dannym.html)

webgraph 11.01.2023 14:46

Как хранить и иметь быстрый доступ к данным?
 
Имеется простая структура списка операций, в которой ключ - порядковый номер операции, а значение - набор данных (на данном этапе несколько реализаций):

// Версия с объектом в значении
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 — и я отвечу: потому что логика максимально простая — мы можем только добавлять или читать данные (возможность обновления или удаления полностью отсутствует).

:)

voraa 11.01.2023 15:11

Цитата:

Сообщение от webgraph
operations.has('uuid-8a24-971f', '234.489274'); // есть ли у участника 'uuid-8a24-971f' операция, в которой параметр value равен '234.489274'

А если есть '234.489274000000000'?
Их все таки как числа или как строки сравнивать?

webgraph 11.01.2023 15:20

Цитата:

Сообщение от voraa (Сообщение 549874)
А если есть '234.489274000000000'?
Их все таки как числа или как строки сравнивать?

Для проведения математических операций значение будет приводиться к виду BigInt. А потом снова в строку (ибо иначе bigint не хранится).

Т.е. по итогу после точки у всех value будет одинаковое количество знаков (или как вариант — хранить без точки, а в целых значениях, но то же в строке - т.к. bigint).

Поэтому думаю актуальнее сравнивать как строки?)

voraa 11.01.2023 15:28

Дробные нельзя в BigInt
А если без точки, то не понятно, где целая, где дробная, и как их складывать и вычитать
Цитата:

Сообщение от webgraph
Для проведения математических операций значение будет приводиться к виду BigInt.

Что должно получиться после приведения
'234.489274000000000'
'234.489274'
'23448.9274'
?

webgraph 11.01.2023 15:29

Цитата:

Сообщение от voraa (Сообщение 549876)
Дробные нельзя в BigInt

Это понятное дело. Суть в том, что после точки количество знаков у всех одинаковое.

webgraph 11.01.2023 15:33

Цитата:

Сообщение от voraa (Сообщение 549876)
Дробные нельзя в BigInt
А если без точки, то не понятно, где целая, где дробная, и как их складывать и вычитать

В общем все числа были приведены к виду BigInt :)) (примеры в шапке были отредактированы)

webgraph 11.01.2023 15:44

Цитата:

Сообщение от voraa (Сообщение 549876)
Дробные нельзя в BigInt
А если без точки, то не понятно, где целая, где дробная, и как их складывать и вычитать

Что должно получиться после приведения
'234.489274000000000'
'234.489274'
'23448.9274'
?

Ну, ваще:

'234.489274000000000' ->
234489274000000000

'234.489274' ->
234489274000000000

'23448.9274' ->
23448927400000000000

Т.е. в вашем случае было принято решение использовать 15 знаков после запятой — значит все числа в этой системе будут иметь 15 знаков после запятой. Это число знаков, очевидно, может быть выбрано только 1 раз.

voraa 11.01.2023 15:45

Цитата:

Сообщение от webgraph
Суть в том, что после точки количество знаков у всех одинаковое.

Т.е например 17 знаков на дробь и 17 знаков на целое
Итого строка длиной 34 знака.
И если целое меньше до дополняется нулями слева до 17 знаков, а дробная часть дополняется нулями справа до 17 знаков
Цитата:

Сообщение от voraa
Что должно получиться после приведения
'234.489274000000000'
'23448.9274'
?

Соответственно получаем строки
'0000000000000023448927400000000000'
и
'0000000000002344892740000000000000'

(Все равно не понятно, как их делить и умножать)

webgraph 11.01.2023 15:49

Цитата:

Сообщение от voraa (Сообщение 549880)
Т.е например 17 знаков на дробь и 17 знаков на целое
Итого строка длиной 34 знака.
И если целое меньше до дополняется нулями слева до 17 знаков, а дробная часть дополняется нулями справа до 17 знаков
Соответственно получаем строки
'0000000000000023448927400000000000'
и
'0000000000002344892740000000000000'

(Все равно не понятно, как их делить и умножать)

А зачем нули перед целыми добавлять О_о Этого делать не нужно.

Ну в общем, уже же решили, что сейчас все числа просто имеют тип BigInt, так что с дробными числами можно успокоиться))

voraa 11.01.2023 15:54

Цитата:

Сообщение от webgraph
А зачем нули перед целыми добавлять О_о Этого делать не нужно.

Т.е если надо сравнить на больше-меньше - надо сначала перевести в bigint? Просто как строки не получится.

webgraph 11.01.2023 15:57

Цитата:

Сообщение от voraa (Сообщение 549882)
Т.е если надо сравнить на больше-меньше - надо сначала перевести в bigint? Просто как строки не получится.

Примеры были отредактированы. Теперь все значения целочисленные. Да, строку необходимо привести к виду BigInt.

webgraph 11.01.2023 16:02

Цитата:

Сообщение от voraa (Сообщение 549882)
Т.е если надо сравнить на больше-меньше - надо сначала перевести в bigint? Просто как строки не получится.

А для чего сравнивать на большее-меньшее? Если достаточно сравнить === ? типа

//

if('100' === '100') return true;

// а если всё таки требуется математические сравнения больше-меньше, то тогда определенно надо привести к виду BigInt

voraa 11.01.2023 16:02

Цитата:

Сообщение от webgraph
по итогу при общем большом количестве операций думаю могут возникнуть трудности. и оптимальнее все таки искать value у конкретного участника:

Лучше, конечно иметь весь список операций, которые могут понадобиться. Какие частые и надо проводить быстро, какие редкие и скоростью можно пожертвовать.
Из того, что написано может быть такая схема

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

webgraph 11.01.2023 16:24

Цитата:

Сообщение от voraa (Сообщение 549885)
Лучше, конечно иметь весь список операций, которые могут понадобиться. Какие частые и надо проводить быстро, какие редкие и скоростью можно пожертвовать.
Из того, что написано может быть такая схема

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

Получается параметр operations позволит выводить весь список операций или определенное количество по конкретному UUID, а параметр values — искать значение value у этого участника.

Операции добавлять с помощью unshift().

Массивы в данном случае действительно быстро будут работать? А то мы выяснили, что они ппц какие медленные.

voraa 11.01.2023 16:33

Цитата:

Сообщение от webgraph
Операции добавлять с помощью unshift().

Почему? обычный .push()

Цитата:

Сообщение от webgraph
Массивы в данном случае действительно быстро будут работать?

Вот для этого и нужен весь список операция, что бы понять, какие операции требуют перебора по массивам.
Из того. что вы написали в первом посте, я операций поиска не вижу. Только взять n последних для
Цитата:

Сообщение от webgraph
3. Вывести список всех операций (или их часть) по конкретному участнику


webgraph 11.01.2023 16:38

Цитата:

Сообщение от voraa (Сообщение 549887)
Почему? обычный .push()


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

Зачем .push()? Новые операции добавлять в начало массива. А потом через цикл for вывести , например, 100 первых операций из массива — что будет являться 100 последними операциями участника.

webgraph 11.01.2023 16:40

Цитата:

Сообщение от voraa
Вот для этого и нужен весь список операция, что бы понять, какие операции требуют перебора по массивам.

мм что-то не до конца понятно что вы имеете ввиду?))

voraa 11.01.2023 16:43

Вопрос, что чаще делается.
Если добавляем гораздо чаще, чем получаем последние n, то лучше push.
А получить время от времени последние можно через slice(-n)

webgraph 11.01.2023 16:45

Цитата:

Сообщение от voraa (Сообщение 549890)
Вопрос, что чаще делается.
Если добавляем гораздо чаще, чем получаем последние n, то лучше push.
А получить время от времени последние можно через slice(-n)

А, типа при unshift() — происходит смещение индексов. А при push() - просто добавление.

"получить время от времени" — это о чем вообще?)))

webgraph 11.01.2023 16:46

Цитата:

Сообщение от voraa
то лучше push

Чувствуется, что здесь актуальнее использовать List ?)))

voraa 11.01.2023 16:47

Цитата:

Сообщение от webgraph
мм что-то не до конца понятно что вы имеете ввиду?))

Вы в первом посте перечислили все операции?
Если так, то для value (который внутри мэпа по участникам), ненужно другого мэпа с массивом, достаточно set, что бы проверить, есть ли операция с таким value у данного участника

voraa 11.01.2023 16:52

Цитата:

Сообщение от webgraph
Чувствуется, что здесь актуальнее использовать List ?)))

Мне не чувствуется. Все операции только добавить в конец и взять n последних. Ни удалений, ни вставок... Зачем List? Массив прекрасно справится.
Если вдруг понадобится какой то поиск, то все равно перебор, что у массива, что у списка.

webgraph 11.01.2023 17:06

Цитата:

Сообщение от voraa (Сообщение 549894)
Мне не чувствуется. Все операции только добавить в конец и взять n последних. Ни удалений, ни вставок... Зачем List? Массив прекрасно справится.
Если вдруг понадобится какой то поиск, то все равно перебор, что у массива, что у списка.

Может потому что в List быстрее добавляются элементы, чем в Array?)

voraa 11.01.2023 17:13

Цитата:

Сообщение от webgraph
Может потому что в List быстрее добавляются элементы, чем в Array?)

Нет. Это моментальная операция, которую вообще не стоит учитывать.
(На порядок быстрее, чем всякие переводы из строк в BigInt)

webgraph 11.01.2023 17:18

Цитата:

Сообщение от voraa (Сообщение 549896)
Нет. Это моментальная операция, которую вообще не стоит учитывать.
(На порядок быстрее, чем всякие переводы из строк в BigInt)

Всмысли нет? хахах)) мы же вместе проводили 100500 тестирований.

Ну а как вы предлагаете ещё хранить BigInt? Все эти данные необходимо ещё и экспортировать, импортировать. Конечно, при импорте актуально сразу в BigInt преобразовывать. Но в конечном-то счете это будет храниться в обычной строке.

voraa 11.01.2023 17:36

Цитата:

Сообщение от webgraph
Всмысли нет? хахах)) мы же вместе проводили 100500 тестирований.

Мы проводили сравнение не push, а вставку в середину массива, используя splice и удаление, и вставку и удаление со списком.
Тут список быстрее. Не надо ничего двигать, а только переписать 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');

webgraph 11.01.2023 17:41

Цитата:

Сообщение от voraa (Сообщение 549898)
Мы проводили сравнение не push, а вставку в середину массива, используя splice, и вставку в середину списка.
Тут список быстрее. Не надо ничего двигать, а только переписать 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');

Да, про Push и Shift — это очевидные вещи. Но если сравнить вставку в Array и вставку в List ?))

voraa 11.01.2023 17:43

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

webgraph 11.01.2023 17:45

Цитата:

Сообщение от voraa (Сообщение 549900)
Вставка (в смысле в середину) для списка быстрее. (ну для длинных массивов).
В конец дописать - массив однозначно быстрее.

Причем здесь середина? Мы же добавляли в список в конец. А не в середину.

voraa 11.01.2023 17:54

Цитата:

Сообщение от webgraph
Ну а как вы предлагаете ещё хранить BigInt?

Все от величин зависит.
Если скажем взять обычные целые. Максимальное целое число, которое может быть точно представлено в js -
Number.MAX_SAFE_INTEGER = 2**53 - 1 = 9,007,199,254,740,991
16 разрядов однако.
Может этого будет достаточно?

webgraph 11.01.2023 18:02

Цитата:

Сообщение от voraa (Сообщение 549902)
Все от величин зависит.
Если скажем взять обычные целые. Максимальное целое число, которое может быть точно представлено в js -
Number.MAX_SAFE_INTEGER = 2**53 - 1 = 9,007,199,254,740,991
16 разрядов однако.
Может этого будет достаточно?

Если бы было достаточно, тогда не приходилось бы прибегать к BigInt.

webgraph 11.01.2023 18:03

Цитата:

Сообщение от voraa
В конец дописать - массив однозначно быстрее.

Чаще всего Array действительно быстрее)

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');


Насколько корректно проведено тестирование?)

webgraph 11.01.2023 23:03

Цитата:

Сообщение от webgraph
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

voraa, а на сколько большой может быть такой массив данных?

voraa 12.01.2023 09:58

Цитата:

Сообщение от webgraph
voraa, а на сколько большой может быть такой массив данных?

Какой именно?
Если массив operations, то это зависит от ваших условий. Сколько там операций у вас идет?
Про Map точно не знаю ограничений, но пару миллионов точно влезает.
Где то читал про объекты, вроде нельзя больше 8 миллионов полей. Думаю для Map примерно такое же ограничение.
Ну и разумеется, должно хватить памяти у компа.

webgraph 12.01.2023 19:32

Цитата:

Сообщение от voraa (Сообщение 549906)
Какой именно?
Если массив operations, то это зависит от ваших условий. Сколько там операций у вас идет?
Про Map точно не знаю ограничений, но пару миллионов точно влезает.
Где то читал про объекты, вроде нельзя больше 8 миллионов полей. Думаю для Map примерно такое же ограничение.
Ну и разумеется, должно хватить памяти у компа.

Суть в том, что изучили все возможные БД и SQL, и NoSQL, и NewSQL... И реляционные, и документоориентированные, и колоночные...

Вот колоночные как-то больше всего подходят (именно архитектура). Но по итогу ни одна из них не подходит.

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

Либо, допустим, построчно хранить операции в файле — типа как лог. А отдельно ещё сделать в виде колонок — чтобы иметь мгновенный доступ к требуемым данным. Например, в одной колоночной БД — реализован механизм, который создаёт виртуальные колонки, отсортированные по конкретной колонке:

Н-р, в нашем случае есть 3 основных столбца — from, to, amount. Если представить их в хронологическом порядке, то в столбце amount всё идет как попало и сложно что-либо найти быстро. Для этого создаётся отдельная колонка (или "представление") в котором все данные отсортированы по порядку — как результат мы можем использовать либо бинарный поиск, либо ваще интерполирующий (вот бы ещё понять как его на строки перевести).

По итогу ограничение данных просто отсутствует.

voraa 12.01.2023 19:58

Тогда только думать про БД. С индексами и все как положено.
Рассмотрите Mongo, как вариант.
Но с любой БД вы не получите таких скоростей, как с хранением в оперативной памяти
Все таки хранение на диске, считывание индексов, самих данных....
О временах в пределах нескольких мс на операцию можно не мечтать.

webgraph 12.01.2023 20:15

Цитата:

Сообщение от voraa (Сообщение 549917)
Тогда только думать про БД. С индексами и все как положено.
Рассмотрите Mongo, как вариант.
Но с любой БД вы не получите таких скоростей, как с хранением в оперативной памяти
Все таки хранение на диске, считывание индексов, самих данных....
О временах в пределах нескольких мс на операцию можно не мечтать.

Mongo была вдоль и поперек рассмотрена. Это вообще не то.

Хм, а если тогда использовать несколько серверов? Типа в тех же колоночных БД данные хранятся в файлах размером до 256 МБ.

Получается вместо файла просто будет отдельный веб-сервер. :D

webgraph 12.01.2023 20:22

Цитата:

Сообщение от voraa
считывание индексов

Кстати, в колоночной БД — колонки сами по себе являются индексами.

webgraph 12.01.2023 20:23

Цитата:

Сообщение от webgraph
использовать несколько серверов?

voraa,
или как-то может виртуальных серверов?

voraa 12.01.2023 20:25

Не знаю. Совсем не спец по архитектуре серверов. В любом случае надо будет смотреть, что там с временами запросов будет.


Часовой пояс GMT +3, время: 08:05.