Страницы

Поиск по вопросам

Показаны сообщения с ярлыком хранение-данных. Показать все сообщения
Показаны сообщения с ярлыком хранение-данных. Показать все сообщения

среда, 4 марта 2020 г.

Максимально разумный размер файла xml для хранения данных

#c_sharp #xml #хранение_данных


В программе использую Datatable. При запуске считываю в таблицу данные из xml файла.
В ходе работы программы изменяемые данные снова пишу в xml файл. При этом данные -
аккумулируются, соответственно файл растёт. Также данные датируются и устаревают со
временем. Возникла мысль, проверять размер файла xml и в случае превышения некоего
объёма - старые данные из него вычищать. Просьба посоветовать, до какого предела можно
позволять разрастаться xml файлу для шустрой работы с ним. Понимаю, что не обязательно
привязываться к размеру файла и данные можно чистить просто по датам, но хочу всё же
отталкиваться именно от размера файла
    


Ответы

Ответ 1



XML - громоздкий формат, что ведёт к большому размеру файлов и большому потреблению места на носителях. Давайте рассмотрим способы уменьшения потребления ресурсов. Радикальный способ: рассмотрите возможность отказа от XML и перехода к другому формату файлов: это могут быть json, yaml, бинарные форматы, наподобие protobuf, и прочие. Кроме уменьшения размеров это может привести и к большей скорости обработки файлов (запись-чтение-парсинг), и к более быстрой передаче по сети. К тому же многие из них тоже являются общепринятыми стандартами, наряду с XML. Сжатие. Текстовые форматы почти всегда хорошо сжимаются архиваторами. XML не является исключением. Повторяющиеся теги, само собой, будут сжаты архиватором в один экземпляр. В дотнете легко и просто можно использовать GZipStream, DeflateStream. Конечно, можно применять и сжатие в форматы 7Z, Zip, RAR и т. п. Можно включить сжатие диска на уровне операционной системы (приложение даже не будет знать об этом). Естественно, сжатие применимо для любых форматов. P.S. Файлы офисного пакета: xlsx, docx - это зазипованный xml. Кодировка файлов. Для текстовых форматов выбор правильной кодировки является очень важным делом. Если взять однобайтовую ANSI - то размер файлов будет минимальный, но мы будем ограничены в количестве возможных символов. Соответственно, если нам нужны символы из разных алфавитов и другие символы за пределами восьми бит, придётся взять многобайтовую кодировку. Какую? Например, UTF-32: весь диапазон Юникода в нашем распоряжении! Однако, четыре байта на символ - не слишком ли расточительно? Стандартный и самый распространённый вариант - UTF-8. Пожалуй, эта кодировка оптиимальна для XML: часто повторяющиеся в нём символы: <, >, ", ', =, ? - будут кодироваться одним байтом. А если ещё и названия тегам и атрибутам давать английские, то они тоже будут кодироваться компактно. Однако, в некоторых случаях UTF-16 может оказаться выгодней: там где в UTF-8 на один символ может понадобиться три и более байтов, в UTF-16 будет достаточно двух. Но это нужно уточнять на деле, в зависимости от предполагаемых для хранения данных. А если ещё и вместо идиотского представления конца строки двумя символами LR/LF (\r\n), как зачем-то сделано в Windows, использовать один символ, как сделано в Unix/Linux/MacOS, то можно выиграть ещё несколько процентов. Тут вопрос в том, способны ли на это используемые сериализаторы/парсеры. Не указывать BOM. А что - экономим ещё пару байтов в каждом файле... Размер кластера файловой системы. Как известно, размер кластера может варьироваться в разных системах. Например, от 512 байт до 32 или даже 64 кб. Стандартный размер по умолчанию обычно 4 кб. Таким образом, если большинство файлов у нас имеют небольшой размер, не более нескольких сотен байтов (а самих файлов очень много), то на каждый из них будет выделено всё равно по одному кластеру: лишний расход места на харде очевиден. В данной ситуации может оказаться очень выгодно переформатировать диск под маленький размер кластера. И наоборот, если файлы у нас большие, минимум сотни килобайт, то для них будет выгоднее форматирование файловой системы под большие кластеры: чем меньше количество кластеров, тем меньше места отводится под файловую таблицу. К тому же, на больших кластерах происходит быстрее чтение-запись больших файлов. Нельзя не отметить, что архивирование довольно эффективно решает эту проблему: файлы в архиве хранятся последовательно как единое целое, поэтому маленькие файлы не будут занимать по целому кластеру. Причём можно применять даже архивирование без сжатия. Это всё были административные меры, применимые к любому формату и непосредственно не связанные с XML. Теперь переходим к нему самому. Следует отказаться от форматирования с индентацией. Такой формат удобнее для чтения человеком, но любой специализированный xml-редактор может на лету выполнять форматирование. Без отступов будет значительная экономия на пробельных символах. Bob 42 Bob42 В крайнем случае, использовать один TAB вместо нескольких пробелов. Вместо узлов-тегов можно использовать атрибуты: название будет представлено всего в одном экземпляре, вместо двух. Bob42 Ещё раз упомяну о желательности имён на английском языке: это может положительно сказаться на кодировании букв всего одним байтом в случае UTF-8. Хранение XML-схем. Иногда применяется помещение XmlSchema в файл XML с данными. Это зачастую довольно удобно, но при наличии большого количества однотипных файлов, валидируемых этой схемой, приводит к бездумному повторению одной и той же схемы во всех файлах. Следует хранить xsd в одном экземпляре, отдельно от xml-файлов с данными. Значения по умолчанию. Несложно догадаться, что если данные известны заранее, то их можно не сохранять в каждом файле. Кроме ручной реализации этого способа, при самостоятельной записи и чтении, есть и автоматические. Пример сериализации со значением по умолчанию. На помощь нам приходит XmlSerializer, с его способностью не записывать свойства, помеченные атрибутом DefaultValue (для десериализации дефолтные значения необходимо задавать в конструкторе): public class Person { public Person() { Gender = "female"; } public string Name { get; set; } [DefaultValue("female")] public string Gender { get; set; } } var list = new List { new Person { Name = "Bob", Gender = "male" }, new Person { Name = "Alice", Gender = "female" } }; xs.Serialize(Console.Out, list); Вариент с использованием XmlSchema, в которой можно указывать дефолтные значения (что является очень мощным способом, хоть и часто критикуемым). Bob 42 Alice 21 Видите суслика? А он есть! В xsd задано значение по умолчанию gopher для атрибута pet. Использовать короткие префиксы пространств имён. Наиболее частый неймспейс сделать namespace by default (префикса вообще не будет). Bob 42 Bob 42 Выкинуть все комментарии (по возможности). Alice 21 Для однотипных данных не нужны комментарии в каждом файле. Достаточно одного экземпляра описания в документации (схеме). Далее я мог бы рассказать о совсем уж специфичных способах работы с xml, приводящим к нехватке памяти при использовании потоковых (sic!) XmlReader/XmlWriter. Однако, сообщение уже превысило разумные пределы. Скажу лишь, что нужно заранее тщательно проектировать структуру xml, чтобы впоследствии можно было извлечь желаемые данные за один проход. Например, часто встречается нечто подобное: something 1 Данные нужно извлечь по индексу, который расположен в другой части файла. Вот и приходится либо в два прохода это делать, либо использовать словарь/хэшсет для аккумуляции информации. Не удержался. Ещё экзотический пример. Часто люди задаются вопросом: как дописывать информацию в файл xml? Вариант для извращенцев: var settings = new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true }; // Создаём файл xml. using (var fs = new FileStream("test.txt", FileMode.Create)) using (var writer = XmlWriter.Create(fs, settings)) { writer.WriteStartElement("root"); for (int i = 0; i < 3; i++) writer.WriteElementString("foo", i.ToString()); } // Дописываем информацию в конец файла. Без его перезаписи! using (var fs = new FileStream("test.txt", FileMode.Append)) using (var writer = XmlWriter.Create(fs, settings)) { writer.WriteComment("bar"); writer.WriteComment("baz"); } // Читаем из конца. var xml = XDocument.Load("test.txt"); var last = xml.LastNode; // Или так. var comments = xml.DescendantNodes().OfType(); foreach (var c in comments) Console.WriteLine(c.Value); Дописать информацию в начало xml-файла без его перезаписи? Да не вопрос! Опять на помощь приходит XmlSchema и значения по умолчанию. Предлагаю додуматься самостоятельно, как это сделать. А мне пора спать. Резюмируя: при должной сноровке можно комфортно (ну-у...) работать с файлами любого размера.

вторник, 25 февраля 2020 г.

Как правильно хранить файлы на веб-сервере?

#php #файлы #хранение_данных


Я бы хотел задать кое-какие вопросы о построении каталогов и хранении в них файлов
(картинок, к примеру).

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

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

Многие советуют делать это след. образом:
Вкратце опишу, генерируем название файла с помощью md5 хэша.
Затем, берем первые 2 символа из название и создаём папку и туда помещаем все файлы,
которые начинаются на одни и те же символы, подобным образом мы можем создать 256 папок
в которые, к примеру, мы можем поместить по 1000 файлов. Разумеется, можно делать вложенные
уровни из 3 и 4 символа из 5 и 6 и тд.

Но я не могу понять, что будет, если одна из папок забьется гораздо раньше чем другие?
Как в таком случае быть?

Мой вариант, который пришел в голову (Думаю, я не первый):
Создаем папку с числом текущего года, к примеру, затем текущего месяца и в папке
с числом месяца создаем папку с днем месяца и в ней создаем папку с названием 1 и забиваем
её до тех пор пока в ней не будет 1000 файлов, затем создаем папку с числом 2 и забиваем
её, ну и в таком же духе и дальше.

Выходит, если мы будет хранить в папке до тысячи папок и в этих папках до тысячи
файлов, то у нас в одной папке которая названа числом месяца может храниться до 30
000 000 файлов. После того как месяц закончился переходим к другому, закончился год,
переходит к другому...
Файловой системе в таком случае не придется тонны файлов разгребать, максимальное
число файлов с которыми придется ей работать это 1000

Для наглядности:
2015/04/15/1

Забиваем папку с названием 1 до 1000 файлов, затем создаем папку с названием 2, после
3 и так до 1000

Закончился день, тогда создаем в папке 04 папку 16 и дальше по описанному принципу
работаем.
Разумеется, папки создаются только тогда, когда в них грузятся файлы.

Какие у вас мысли по этому поводу? Какую структуру хранения файлов используете вы?
И чем она хороша? Приемлема ли та структура которую описал я, с вашей точки зрения?
Что скажете по поводу производительности?
    


Ответы

Ответ 1



Ответ достаточно общий, без всякой привязки к PHP. Если требуется поиск файлов по имени, то я бы попробовал все же сделать структуру, основанную на MD5 (или другой хэш-функции) в 16-ричном виде (для MD5 все имя это 32 символа от 0 до f), только не с жесткой организацией уровней каталогов, а с динамической. Имена каталогов задаются, например, тройками символов. Соответственно, каталог может содержать до 4096 других каталогов. Для начала начинаем помещать в каталог сами файлы (с MD5-именем). Можно еще завести там служебный файл для отображения хэша в имя (может пригодиться, если захотите узнать реальные имена хранимых файлов) и синонимов (вдруг такое произойдет). Впрочем, структура такого файла -- это отдельный вопрос. Когда в каталоге соберется 4096 файлов мы проводим реорганизацию. Делаем по первой тройке символов имен файлов каталоги и перемещаем файлы в них. Надеюсь, далее очевидно.

Ответ 2



что будет, если одна из папок забьется гораздо раньше чем другие? Странный вопрос. С какой стати одна должна забиться раньше? Это ведь рандомный хэш, а не пользовательское имя файла на IMG_. Ну да, хэш надо генерировать с умом. Но это проблема генерации хэша, а не самого принципа. То есть я не понимаю логику, стоящую за этим доводом. Это все равно что решать, чем есть борщ, и отказаться от ложки только потому, что кто-то, возможно, её в руках не умеет держать. Ну так надо одного криворукого учить, а не всем хлебать через край. Чем система проще, тем она лучше работает. В простой системе нечему ломаться. Если изначально задать алгоритм, который не будет требовать никаких пересчетов и реорганизаций, то он и будет работать без необходимости каждый раз(!) файлы в папке пересчитывать. Самый оптимальный вариант - это md5() от содержимого. Обеспечит не только вполне случайное распределение, но и сэкономит место в случае дублей (только при удалении надо быть внимательным и смотреть нет ли других записей, ссылающихся на тот же файл). Плюс можно перевести хэш из 16-ричной системы в 36-ричную, задействовав все буквы алфавита, а не только первые 6. Что сократит длину хэша и одновременно увеличит количество вариантов для разбивки, уменьшая необходимую глубину папок.

Ответ 3



Однажды я делал фотохостинг, и я делал следующим образом: По задумке сайта, для каждой картинки есть ссылка в формате example.com/vnjum. Как в сокращалке ссылок. Тогда картинка сохраняется в папку example.com/images/v/n/j/u/m/image.jpg Тогда и из ссылки на сайт можно получить ссылку на сам файл и наоборот) Вот вам еще один метод. В документа будет два идентификатора - время загрузки и случайное число от 100 000 до 999 999. Для примера, время будет 1540670259710 (это UNIX-формат), а случайное число - 125793. Переводим дату в 36-ричную систему (в JavaScript - .toString(36)), получаем jnrvakn2. Делим на куски по 1-2-2-3 символа. Получаем j-nr-va-kn2. Далее берем наше случайное число - 125793. В 36-ричной системе это будет 2p29. Получаем адрес /j/nr/va/kn2/2p29/filename.jpg

вторник, 24 декабря 2019 г.

Правильное хранение лайков/дислайков (голосования) в БД

#mysql #база_данных #хранение_данных


Есть сайт, на котором должна быть организована система голосования (лайк/дислайк).
Если записывать в БД информацию в виде номер_записи - id_пользователя, то вроде бы
всё хорошо. Но. Возьмем 100 записей и 100 пользователей. Каждый из них проголосует
за каждую запись, в итоге в БД будет содержаться 10.000 записей, а это проблема.
Почитав немного, изменил таблицу и она стала вида:
номер_записи - id_всех_пользователей_которые_голосовали. При тех же условиях вместо
10.000 получим всего 100 записей в БД.

Добавление нового id пользователя происходит так:


забираем всю строку из БД, где номер записи равен N
SELECT FROM WHERE users_id LIKE '%id_пользователя%' - запрос,
чтобы проверить существование уже этого id в строке
в случае успешной проверки добавляем в конец строки новый id и
заносим в БД новую строку.


Насколько это правильный подход к хранению такого рода информации? Какие есть альтернативы?
Готов услышать и принять к сведению.
    


Ответы

Ответ 1



Прикинем, как часто что требуется. сумма (или две суммы: за и против) по конкретному голосованию – супер-часто – при каждом открытии страницы с этим голосованием. голосовал ли данный юзер за данный опрос? – реже, при приёме нового голоса. кто проголосовал за / против за данный вопрос? – ещё реже, если вообще это открытая инфа. как и где голосовал данный юзер – тоже, вероятно, совсем редко, если вообще. Итого, текущие суммы храним в каком-нибудь быстром кэше, типа memcached/apc/redis. При поступлении нового голоса в БД надо проверить, голосовал ли? и сохранить голос + обновить кэшированную сумму. Хорошо иметь отдельную таблицу только для голосов: id_опроса id_юзера голос_плюс_минус (1 бит) Основной индекс составной - по обоим id - т.к. будем искать и по вопросу-юзеру (можно ли принять голос - может быть только одна запись с парой qid,uid) и по опросу (кто-как). И ещё индекс только по юзеру (где голосовал). Да, будет запись в БД и двух индексах на каждый отданный голос. Хоть 10 млн. – это нормально. Каждая запись – два 32-битных целых и 1 бит. Когда проект вырастет – станете горизонтально масштабировать – скажем, id опросов меньше X уедут на доп. MySQL сервер.

воскресенье, 15 декабря 2019 г.

Подгрузка файла в память

#cpp #массивы #хранение_данных


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

Есть вариант держать fstream все время открытым и выбирать данные используя seekg().
Будет ли в таком случае весь файл загружен в память? 
    


Ответы

Ответ 1



Файл нужно отобразить на оперативную память (mmap в unix, CreateFileMapping в Windows). Тогда при обращении к нему, ОС будет сама подтягивать с диска нужные куски (причем самым оптимальным образом). В том случае, если физической памяти будет недоставать, ОС выбросит эти страницы из нее (что не займет практически никакого времени), и не будет их долго и мучительно сливать в файл подкачки.

Ответ 2



Будет ли в таком случае весь файл загружен в память? Нет, не будет. Если бы открытие файла с помощью std::fstream требовало загрузки всего содержимого файла в память, то большие файлы открывались бы ну очень долго, а это не так. Поэтому предложенную Вами идею вполне можно использовать для реализации.

суббота, 30 ноября 2019 г.

В каком типе данных хранить ip адреса пользователей в SQL MySQL?

#mysql #sql #хранение_данных


В каком типе данных хранить ip адреса пользователей в SQL MySQL ?
    


Ответы

Ответ 1



Адреса IPv4 храню в поле int unsigned в виде числа. Unsigned обязательно, чтобы на один знак больше влазил. На жёстком диске занимает 4 байта. Что меньше чем char/varchar. Для преобразования из строкового ip-адреса в число есть mysql функция INET_ATON: mysql> SELECT INET_ATON('193.125.99.10'); +----------------------------+ | INET_ATON('193.125.99.10') | +----------------------------+ | 3246220042 | +----------------------------+ 1 row in set (0.00 sec) Для преобразования из числа в адрес - INET_NTOA. Int вмещает до 2147483647 Int unsigned - до 4294967295 Выборки по диапазонам делать удобно. Источник мой :) Хранить данные по ip в varchar - моветон, пригодный только для маленьких проектов. Если у вас миллион посетителей в день, из базы надо выжимать максимальную производительность, а памяти нужно использовать как можно меньше. Поскольку память - это деньги, циклы процессора - это деньги. Миллион посетителей в день вполне может быть если вы делаете не магазинчик, а, допустим, рекламную сеточку, dsp, баннерокрутилку, платежную систему (свою или для банка) и т.д. Миллион посетителей в день - это например, ваш выстреливший стартапчик, развернутый на AWS, лишняя сотня(тысяча?) баксов в месяц, которую вам может сэкономить такая оптимизация никогда не повредит. Использование INET_NTOA/INET_ATON очень легкая, но важная оптимизация. Когда вам потребуется обратиться к БД за айпишником, лучше делать преобразования inet_ntoa/inet_aton до запроса, в php/python/на_чем_вы_там_пишете чтобы использовались индексы бд. P.S. про IPV6 - https://dba.stackexchange.com/a/81402 в версии 5.6 MySQL добавлены функции для работы с ним

Ответ 2



мы храним в varchar, в принципе зачем что-то выдумывать для хранения айпишника? можно конешно хранить как целые числа и использовать функции mysql INET_ATON() и INET_NTOA(). детальнее вот тут http://expange.ru/e/%D0%9A%D0%B0%D0%BA_%D1%85%D1%80%D0%B0%D0%BD%D0%B8%D1%82%D1%8C_IP-%D0%B0%D0%B4%D1%80%D0%B5%D1%81_(MySQL)

Ответ 3



4 поля byte для IPv4 И места меньше занимает и оперировать проще

вторник, 27 ноября 2018 г.

Правильное хранение лайков/дислайков (голосования) в БД

Есть сайт, на котором должна быть организована система голосования (лайк/дислайк). Если записывать в БД информацию в виде номер_записи - id_пользователя, то вроде бы всё хорошо. Но. Возьмем 100 записей и 100 пользователей. Каждый из них проголосует за каждую запись, в итоге в БД будет содержаться 10.000 записей, а это проблема. Почитав немного, изменил таблицу и она стала вида: номер_записи - id_всех_пользователей_которые_голосовали При тех же условиях вместо 10.000 получим всего 100 записей в БД.
Добавление нового id пользователя происходит так:
забираем всю строку из БД, где номер записи равен N SELECT FROM WHERE users_id LIKE '%id_пользователя%' - запрос, чтобы проверить существование уже этого id в строке в случае успешной проверки добавляем в конец строки новый id и заносим в БД новую строку.
Насколько это правильный подход к хранению такого рода информации? Какие есть альтернативы? Готов услышать и принять к сведению.


Ответ

Прикинем, как часто что требуется.
сумма (или две суммы: за и против) по конкретному голосованию – супер-часто – при каждом открытии страницы с этим голосованием. голосовал ли данный юзер за данный опрос? – реже, при приёме нового голоса. кто проголосовал за / против за данный вопрос? – ещё реже, если вообще это открытая инфа. как и где голосовал данный юзер – тоже, вероятно, совсем редко, если вообще.
Итого, текущие суммы храним в каком-нибудь быстром кэше, типа memcached/apc/redis.
При поступлении нового голоса в БД надо проверить, голосовал ли? и сохранить голос + обновить кэшированную сумму. Хорошо иметь отдельную таблицу только для голосов:
id_опроса id_юзера голос_плюс_минус (1 бит)
Основной индекс составной - по обоим id - т.к. будем искать и по вопросу-юзеру (можно ли принять голос - может быть только одна запись с парой qid,uid) и по опросу (кто-как). И ещё индекс только по юзеру (где голосовал).
Да, будет запись в БД и двух индексах на каждый отданный голос. Хоть 10 млн. – это нормально. Каждая запись – два 32-битных целых и 1 бит. Когда проект вырастет – станете горизонтально масштабировать – скажем, id опросов меньше X уедут на доп. MySQL сервер.

суббота, 13 октября 2018 г.

Подгрузка файла в память

Есть большой файл с двумерным массивом. Весь массив целиком загружать в память, я думаю, не стоит (ну очень большой массив). Периодически нужны кусочки данных из разных частей массива. Как это лучше организовать?
Есть вариант держать fstream все время открытым и выбирать данные используя seekg(). Будет ли в таком случае весь файл загружен в память?


Ответ

Файл нужно отобразить на оперативную память (mmap в unix, CreateFileMapping в Windows). Тогда при обращении к нему, ОС будет сама подтягивать с диска нужные куски (причем самым оптимальным образом). В том случае, если физической памяти будет недоставать, ОС выбросит эти страницы из нее (что не займет практически никакого времени), и не будет их долго и мучительно сливать в файл подкачки.

пятница, 5 октября 2018 г.

В каком типе данных хранить ip адреса пользователей в SQL MySQL?

В каком типе данных хранить ip адреса пользователей в SQL MySQL ?


Ответ

Адреса IPv4 храню в поле int unsigned в виде числа. Unsigned обязательно, чтобы на один знак больше влазил.
На жёстком диске занимает 4 байта. Что меньше чем char/varchar.
Для преобразования из строкового ip-адреса в число есть mysql функция INET_ATON
mysql> SELECT INET_ATON('193.125.99.10'); +----------------------------+ | INET_ATON('193.125.99.10') | +----------------------------+ | 3246220042 | +----------------------------+ 1 row in set (0.00 sec)
Для преобразования из числа в адрес - INET_NTOA
Int вмещает до 2147483647 Int unsigned - до 4294967295
Выборки по диапазонам делать удобно. Источник мой :)
Хранить данные по ip в varchar - моветон, пригодный только для маленьких проектов. Если у вас миллион посетителей в день, из базы надо выжимать максимальную производительность, а памяти нужно использовать как можно меньше. Поскольку память - это деньги, циклы процессора - это деньги.
Миллион посетителей в день вполне может быть если вы делаете не магазинчик, а, допустим, рекламную сеточку, dsp, баннерокрутилку, платежную систему (свою или для банка) и т.д.
Миллион посетителей в день - это например, ваш выстреливший стартапчик, развернутый на AWS, лишняя сотня(тысяча?) баксов в месяц, которую вам может сэкономить такая оптимизация никогда не повредит.
Использование INET_NTOA/INET_ATON очень легкая, но важная оптимизация.
Когда вам потребуется обратиться к БД за айпишником, лучше делать преобразования inet_ntoa/inet_aton до запроса, в php/python/на_чем_вы_там_пишете чтобы использовались индексы бд.
P.S. про IPV6 - https://dba.stackexchange.com/a/81402 в версии 5.6 MySQL добавлены функции для работы с ним