Здравствуйте, ищу ответа на свой вопрос так как не знаю как правильнее сделать в данной ситуации.
Имеется готовый проект где сотни активных пользователей которые грузят данные в формате
Строка 1
Строка 2
Строка 3 и тд
Объем данных которые грузятся ежедневно от 100 до 10000 строк. Эти строки являются определенным набором данных которые необходимо куда-то сохранить, сейчас организовано (ничего лучше не придумано прошлыми разработчиками) хранение в файлах. Суть в том, что эти строчки периодически извлекаются по 1-2шт, когда-то больше, а когда-то меньше, можно и всё сразу, не так важно.
Проблема заключается в том, что иногда (непонятно до сих пор из-за чего конкретно) извлечение происходит неудачно, то есть будто файл пустой с данными, но на самом деле данные там есть и по итогу происходит двойное "извлечение" данных, например было 100шт, извлечь надо было 4шт, оно извлечет 8шт, первые 4 будут утеряны. Не зависит от того какие операции сейчас на сервере происходят (выборка SQL), мб бэкап файлов или что-то еще, не играет роли (это уже исследовано за больше чем год, проблема есть и её не смогли решить).
Значит что необходимо сделать сейчас (хранить эти строчки, а их бывает очень много и они весят по 5-10МБ), необходимо каждый раз когда происходит увеличение или уменьшение делать подсчёт этих строчек для того чтобы на стороне проверяющего было понятно, цифра уменьшилась или увеличилась (чтобы в будущем админам понять, надо делать что-то с данными или нет).
Если использовать MySQL то в каком типе данных хранить это? Не поедет БД? Мб что-то есть лучшее для хранения такого формата данных?
Ах да, важно чтобы можно было в любой момент без долгих ожиданий выгрузить эти самые данные грубо говоря как это делает PHP file_get_contents
p.s. сейчас File_get_contents получает содержимое файла через ключи блокировки файла (создается очередь, чтобы параллельно другой процесс не смог ничего сделать). Буду рад выслушать предложения, задачка вроде легкая, но по коду всё ок, даже есть доп.проверки на взятие строк, но они не помогают в решении проблемы...
Дополню, сервер 8 ядер, 16 потоков, SSD (Raid 0 зеркалирование), оперативной памяти свободной много (100гб+)
FanatPHP, вообще можно и по отдельности, только важно чтобы потом можно было разом забрать из этого всего списка как штучно так и несколько штук или все сразу, как уже говорил что с этими данными может работать сразу несколько воркеров и отдача одних и тех же данных в разные воркеры не должно быть, то есть уникальность здесь обязательно в плане забора данных
Настоятельно рекомендую, если это возможно, перенесите обработку строк на клиент, с вероятностью в 99% это не составит труда если вообще требуется (а судя по всему вы просто тупо читаете файл и отдаете его в теле запроса).
Тогда появится возможность передать многопоточную отправку данных веб-серверу, где это реализовано максимально эффективно, а все баги вылизаны годами. Т.е. ваши файлы публикуйте статикой в веб сервере, прямо весь каталог либо создавайте символические ссылки в отдельном публичном каталоге (плохая практика но рабочая) для нужных именно сейчас файлов и с именем, включающим идентификатор сессии (если нужно лимитировать доступ для авторизованных пользователей, создание/удаление симлинка - операция мгновенная, особенно если на рамдиске это делать, если приспичит, можно найти/написать модуль к вебсерверу, управляющий правилом rewrite/location).
Получается вместо отдачи файлов бакэенд должен просто говорить клиентам, какие ссылки нужно открыть, чтобы получить эти файлы (например идентификаторы - они же имена файлов)
Нет, обработка должна быть исключительно на беке т.к. взаимодействие происходит с другими сервисами (обработчиками), а значит и извлечение таких данных только на бэке
Надо изобрести велосипед, через уникальную жопу решив вопросы хранения, которые в БД сто лет как пережеваны и отлажены, и не получить никакого профита, кроме необходимости разбираться с любыми проблемами именно тому, кто этот велосипед писал. Вот это - хорошо!
Adamos, база данных очень хороша когда:
* нужно работать многопользовательском режиме (особенно если есть запись/изменения)
* нужно вести поиск (в т.ч. сложный)
* данные со сложной структурой (и ее нужно запрашивать, хитро фильтровать и прочее)
* нужно единообразии в администрировании
только сравнив недостатки со стоимостью затрат можно выбирать базу данных.
Потому что накладные расходы, создаваемые базами данных могут, могут оказаться выше профита.
Например попробуй поадминистрируй базу данных с терабайтом и больше данных, бакапы, восстановления. Попробуй масштабировать нагрузку у огромной базы, пока с ней работают, подправить структуру и прочее. Кстати если выбирать базу то в данном случае скорее всего лучше не sql а key-value...
Работа с 'файл на объект' это не 'уникальная' а давно проверенная, максимально простая и удобная и главное эффективная технология, да у нее есть недостатки - например отсутствие атомарности операций (особенно при связи с базой) поэтому и вопрос в операциях записи с этими данными, к примеру если источник данных и моменты записи от одного человека (например администратора) то этот недостаток нивелируется и становится терпимым.
prostoprofan, тогда ищи ошибку, сам php очень вылизанный язык, в работе с файлами таких ошибок как ты описал, там сложно встретить
rPman, БД хороша, когда тебе нужно отлаженное решение без сюрпризов. Про SQL никто и не говорил.
А работа с "файл на объект" с ростом количества записей утыкается в ограничения ФС, начинает приносить сюрпризы и тормозить в среднем куда раньше, чем БД.
извлечение происходит неудачно, то есть будто файл пустой с данными, но на самом деле данные там есть и по итогу происходит двойное "извлечение" данных, например было 100шт, извлечь надо было 4шт, оно извлечет 8шт, первые 4 будут утеряны.
Очень путанное объяснение
давай по новой все х*йня (с)
Чем больше знаю, тем лучше понимаю, как мало знаю.
То есть у вас единственный затык - "исчезновение" данных из файлов? Которое по факту оказывается и не исчезновением, потому что данные на месте? Тогда не мучайтесь с адаптацией в БД, однако решайте вопрос с "ложным исчезновением" данных.
P.S. Хотя бы проверьте, что блокировка действительно блокирует.
Я же и сказал, данные исчезают в процессе извлечения... Блокировка действительно работает, иначе бы без нее файлы перезаписывались 100 раз, потому что работа с этим данными идёт очень часто
Суть в том, что эти строчки периодически извлекаются по 1-2шт, когда-то больше, а когда-то меньше, можно и всё сразу, не так важно.
Нужно знать по какому принципу извлекают те самые строки.
Типа grep по строкам?
Если да, то поиск LIKE не особо эффективен.
Или там даты, timestamp?
Тогда лучше хранить в СУБД с колонкой datetime.
А может ключ-значение?
Тогда или две колонки в реляционной СУБД или же k/v хранилище.
Или же в них неструктурированные данные, просто текст? К примеру, журнал (log) может иметь некоторую структуру и парсингом расщепляется на составные части. Graylog, FluentD, ...
Исходя из описания проблемы чувствуется, что СУБД заменит весь этот огород, но нужно знать какие данные внутри и каким образом используются.
Я бы посоветовал расписать в вопросе больше о данных, если это возможно.
Нет, никакой выборки regexp, по времени или же по ID или же еще какому-то принципу нет. Выдёргивается первое N кол-во строк, они могут иметь абсолютно разный формат всегда, поэтому тут выгружать по каким-то особым параметрам невозможно.
Сегодня набор строк состоит из Json, а завтра там просто набор написанный ручками от пользователя, а послезавтра вообще билеберда и пользователь тестирует под себя приложение.
в каждой такой строке есть колонка с названием string_data где может держаться одновременно как я уже говорил от 100 до 10000 строк те самые данные что и надо извлекать иногда)
не нужно хранить строки в поле колонки string_data. Нужно разделить их построчно в отдельную таблицу.
А эти строки данных только добавляются или же часть их может удаляться?
что такое "набор строк состоит из Json"?
это каждая "строка" - это отдельный джейсон объект, или это единый джейсон массив, в котором несколько элементов?
Судя по всему данные представляют из себя рандомную кашу, которая каждый раз по разному обрабатывается, если у вас и json и просто текст. Пока не будет четкой структуры в данных, так и будете маятсо...
FanatPHP, Одиночка Айс, да, daemonhk верно сказал, каждый раз разный формат. А какой там формат в строках не имеет значения, потому что это не играет роли. Мне важно организовать безошибочное хранение таких данных и их выдачу без дублей
Одиночка Айс, Представьте что у вас 100 000 строк, одновременно извлекаются от 10 до 200шт за раз (одновременно). Каким образом делать сверку? Проверять, а нет ли этих данных в прошлых извлечениях? Как я её буду делать? Путем записи хэшей в бд? Как я уже сказал, это 20-30гб данных, эти данные с каждым днём всё прибавляются и прибавляются, то что извлекалось хранится также, но уже в "использованных" данных.
Мне известно, что один из проектов схожей тематики использует хранение в MySQL, поштучно вроде как, НО случилась один раз проблема когда произошла блокировка таблиц, данные выгружались бесконечно (или бд подвисла или что-то еще произошло не знаю), в итоге понесли больше убытки.
Похоже никто тут не понял вашей задачи, нужно пояснить лучше... что является минимальной единицей данных, строка текста? с размером в 10мб? или из 10-мбайтовых файлов извлекаются какие то хаотически расположенные строки?
весят они 10мб - это каждая строка весит или все вместе?
почему постоянно тут пишешь про первые,каждые и т.п. причем тут их порядок?
Очень ответственно подойди к ответу на эти вопросы (ответь на каждый а не на один из них):
* Минимальной единицей запрашиваемых данных является строка? какой средний и максимальный ее размер?
* как вы храните строки сейчас? в одном файле куча строк или по файлу на строчку?
* как происходит выбор, какую строчку нужно загружать? строки определены в какой то порядок и нужно брать X первых из него? порядок всегда один и тот же или постоянно меняется?
* как происходит редактирование данных? есть ли добавление новых строк? изменение имеющихся? удаление?
p.s. у меня тут php скрипты перебирают данные, которые в памяти занимают до десяти гигабайт оперативной памяти, все прекрасно работает. это я так к слову о размерах
rPman, да, строка, длина строки минимально от 3х символов, Макс длина до 20000 символов.
Хранение сейчас куча строк в одном файле.
Берутся всегда первые Х строк, порядка нет. Новые строки добавляются или снизу или сверху в зависимости от того как быстрее их нужно будет извлечь на обработку.
Да редактирование есть, через простой html textaera, удаление тоже есть путем либо отправки пустого textarea или нажав кнопку удалить, файл просто удаляется полностью
все ясно, из-за этого способ хранения в файлах (добавление их в начало файла) убивают на корню все профиты от этого, так как файловая система имеет плохую атомарность (при добавлении в конец файла это не проблема), с высокой вероятностью у вас проблемы именно в момент внесения изменений в файлы одновременно с попытками чтения их.
Переделывайте на sql базу данных, где записи - ваши строки, как минимму это будет проще
prostoprofan, и как это определили? полные логи с таймингами типа начал запись в файл, закончил запись в файл, начал читать файл, закончил читать файл?
Очень сложно ошибиться в простом коде чтения N первых строк файла
а как мне потом отредактировать на бэке эти строчки? Как сделать упдейт строк если оно в 1 инпуте (textarea) выводится
если использовать sql базу для хранения, то редактирование становится легким, порядоковый номер строк правится сразу в момент редактирования в атомарной транзакции и блокировать ничего не по требуется
rPman, У нас есть лог обновления каждого файла. Поэтому когда происходит извлечение и есть потеря, то последнее обновление файла (добавление данных в файл) было за долго до момента извлечения. От часа и больше.
Что по поводу SQL, представим есть таблица SQL, в ней есть ID, name файла и str_data колонки
Мы грузим 1000 строк, они сохранились в бд.
Как потом из textarea удалить несколько строк и обновить это в бд?
таблица: data(id, name, str_data,line_num) где line_num - номер строки
Каждый раз как строки приходят от клиента, нужно пересоздать данные в базе, простым delete from where name=... и insert into ... (можно сделать запрос на чтение, сравнить и обновить данные, пересчитав line_num
- это меньше грузит базу на запись но больше на чтение плюс нагрузка на бек по памяти и процессору) плюс правильно локи настроить (или велосипедить 'блокировку' объекта по name)
Пишите на MySQL. Самое верное дело. Там потерь не бывает. Движок InnoDB.
По структуре таблиц зависит от исходных данных.
Напишите сколько у вас примерно файлов сейчас. И какой прирост - т.е. в каком количестве они добавляются ежедневно. Тогда можно будет прикинуть структуру.
Второй вопрос - как нужно делать выборку этих строк? По текстовому поиску или ещё как-то? По заголовкам имен файлов или по всему тексту?
Файлов сейчас ~ 50 000
Каждый файл от 10 до 10000 строк
Ежедневно +- 100-200 раз перезапись файлов, а соответственно новые строчки, могут как и новые файлы создаваться, так и перезаписываться старые, это особо роли не играет т.к. там файлы пронумерованы с идентификаторами, путаницы где что нет.
Выборка простая, последние N строчек.
А вот добавление строчек должно быть как в самое начало "файла" со строками так и в конец, чтобы потом в будущем получая N строк они были именно так как и расположены
Файлов 50к, значит каждый файл в отдельной таблице хранить не будем, т.к. получится очень много таблиц. Файл до 10к строк - это нормально для БД, если каждую строку будем хранить в отдельной записи. Оптимальным будет хранение нескольких файлов в одной таблице. Но не всех. Поэтому всю информацию нужно разделить на несколько таблиц по какому-то признаку. Раз ежедневный прирост неизвестен, то я предполагаю, что все 50000 были созданы одномоментно. Поэтому их можно разбить чисто условно по 50 файлов в таблице на 1000 таблиц. В этом случае каждая таблица будет занимать = 50 файлов * 10к строк * 1к в строке = 500мб. Такой объем для таблицы вполне реален. Предлагаю такую структуру БД:
`tables` - будет хранить список таблиц (поля `id`, `name`)
`files` - будет хранить список файлов (поля `id`, `table_id`, `name`), здесь по id файла можно будет узнать id таблицы, а по ней имя таблицы
`t_0001` - содержимое файлов первой таблицы (поля `file_id`, `line`, `text`); здесь в поле `text` хранится содержимое каждой строки, `line` - порядковый номер строки, он будет проиндексирован составным ключом PRIMARY KEY (file_id, line), можно будет по этому ключу выбрать первые или последние строки.
Со структурой базы можно поиграться здесь через какой-нибудь клиент, например программу HeidiSQL:
- сервер: habr.atou.ru
- порт: 3311
- пользователь: habr_956597
- пароль: 9D4z3R4b
- база данных: habr_956597
Вот дамп базы:
CREATE TABLE IF NOT EXISTS `files` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID файла',
`table_id` int unsigned NOT NULL DEFAULT '0' COMMENT 'ID таблицы',
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'Имя файла (если нужно)',
PRIMARY KEY (`id`),
KEY `files_table_id_index` (`table_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Список файлов';
INSERT INTO `files` (`id`, `table_id`, `name`) VALUES
(1, 1, 'Имя файла 1'),
(2, 1, 'Имя файла 2');
CREATE TABLE IF NOT EXISTS `tables` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID таблицы',
`name` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'Имя таблицы',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Список таблиц';
INSERT INTO `tables` (`id`, `name`) VALUES
(1, 't_0001'),
(2, 't_0002');
CREATE TABLE IF NOT EXISTS `t_0001` (
`file_id` int unsigned NOT NULL DEFAULT '0' COMMENT 'ID файла',
`line` int NOT NULL DEFAULT '0' COMMENT 'Порядковый номер строки',
`text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Текст строки',
PRIMARY KEY (`file_id`,`line`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Первая таблица для хранения 50 файлов';
INSERT INTO `t_0001` (`file_id`, `line`, `text`) VALUES
(1, 1, 'Строка 1 файла 1'),
(1, 2, 'Строка 2 файла 1');
CREATE TABLE IF NOT EXISTS `t_0002` (
`file_id` int unsigned NOT NULL DEFAULT '0' COMMENT 'ID файла',
`line` int NOT NULL DEFAULT '0' COMMENT 'Порядковый номер строки',
`text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci COMMENT 'Текст строки',
PRIMARY KEY (`file_id`,`line`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Первая таблица для хранения 50 файлов';
P.S. Я ещё прикинул, как можно быстро добавлять строки в начало файла, чтобы не перенумеровывать строки, которые ниже. Убираем у поля `line` свойство UNSIGNED (беззнаковое) и тогда при добавлении строк в начало мы будем их нумеровать в обратную (отрицательную) сторону от нуля. И тогда перенумеровать уже сохраненные строки не придется; так что все картинка сложилась; вот вам готовое решение по БД, написать клиента на PHP - это уже дело техники.
Алексей Смирнов, А если хранить все тоже самое что есть сейчас, но в общей таблице (сделали подсчёт всех файлов которые уже были выгружены за все время (4+года) то там больше 30гб). Строчки которые лежат сейчас и еще не извлекались из файлов весом на ~ 4гб. Кол-во строк не считали.
В одну таблицу помещать плохая идея, да?
Лучше не рисковать с хранением в одной таблице. Хоть спецификация и допускает хранение больших объемов данных, но лучше не рисковать, где-нибудь это все равно боком обернется. И чисто из опыта говорю, что 500Мб для таблицы - это нормально даже для простенького сервера с минимальной конфигурацией железа и объемом оперативной памяти. А что может произойти при запросе SELECT из таблицы в 30Гб не могу заранее сказать. И она будет расти.
И второй момент - у таблицы будет индекс. До определенного момента СУБД MySQL при запросах SELECT держит этот индекс в оперативной памяти (т.е. вся таблица ID помещается в оперативную память) и поиск производит в ней, это значительно повышает скорость. А если индекс не помещается, СУБД начинает скидывать его блоками на диск и искать уже поблочно. Это сильно замедляет скорость выполнения запроса. Что может приводить к зависанию всего сервера и простою других запросов в очереди.
Поэтому наращивать базу таблицами - более безопасный вариант. По аналогии как и с файловой системой. Достаточно рисковано хранить 50 тыс файлов в одной папке, т.к. обязательно найдутся какие-нибудь программы (проводник Windows, файловый менеджер, которые от такого количество могут "захлебнуться" просто от одного только открытия этой папки на просмотр). Поэтому стараются файлы разбивать по какому-то признаку, например в хронологическом порядке /2021/03, /2021/04 и т.д. Ну зависит от прироста. Если каждый месяц добавляется порядка 1000 файлов, то разбиваем помесячно. Если в год 1000 файлов, то по годам. Если хронологии нет, то разбивается по другому признаку или просто по максимальному количеству в каждой папке.
Запросы INSERT в большой таблице так же начнут тормозить. Т.к. чем длиннее индекс, тем дольше его придется перестраивать после каждой вставки.