![]() |
|
|||
WebMoney: WMZ Z294115950220 WMR R409981405661 WME E134003968233 |
Visa 4274 3200 2453 6495 |
Предположим, что формат файла
OpenDocument
и формат презентации "ODP" OpenDocument были бы построены вокруг SQLite.
Преимущества включали бы:
Обратите внимание на то, что это только мысленный эксперимент. Мы не
предлагаем, чтобы OpenDocument был изменен. И при этом эта статья не критика
текущего дизайна OpenDocument. Пункт этого эссе должен предложить способы
улучшить будущие проекты формата файла.
Формат файла OpenDocument используется для офисных приложений: текстовые
процессоры, электронные таблицы и представления. Это было первоначально
разработано для OpenOffice, но было с тех пор включено в другие приложения.
Например, в
NeoOffice на Mac или в
LibreOffice в Linux и Windows.
OpenDocument Presentation или "ODP" это
ZIP-архив,
содержащий XML-файлы, описывающие слайды презентации и отдельные файлы
изображений для различных изображений, которые включены как часть
презентации. Текстовой процессор OpenDocument и файлы электронной таблицы так
же структурированы, но не рассматриваются в этой статье.
Читатель может легко видеть содержание файла ODP при помощи вывода
"zip -l" Например, следующее вывод из 49 слайдовых презентаций о
SQLite с 2014 SouthEast LinuxFest
:
ODP ZIP включает четыре различных XML-файла:
content.xml, styles.xml, meta.xml и settings.xml.
Те четыре файла определяют расположение слайда, текстовое содержание
и моделирование. Это конкретное представление содержит 62 изображения, в
пределах от полноэкранных картин до символов, каждое сохранено как отдельный
файл в папке Pictures. Файл "mimetype" содержит единственную строку
текста, в которой говорится:
Цель других файлов и папок в настоящее время неизвестна,
но вероятно это не трудно выяснить.
Использование архива ZIP, чтобы заключить в капсулу XML-файлы плюс ресурсы
является изящным подходом к формату файла приложения. Это ясно превосходит
формат двоичного файла. Но использование базы данных SQLite как контейнера
вместо ZIP, было бы более изящным.
ZIP-архив в основном база данных key/value, оптимизированная для случая
write-once/read-many и для относительно небольшого количества различных
ключей (от нескольких сотен до тысяч) каждый с большим BLOB как значение.
Архив ZIP может быть рассмотрен как база данных "груды файлов".
Это работает, но у этого есть некоторые недостатки относительно
базы данных SQLite:
Инкрементное обновление трудно.
Трудно обновить отдельные записи в архиве ZIP. Особенно трудно обновить
отдельные записи в архиве ZIP способом, который не уничтожает весь документ,
если компьютер выключается посреди обновления. Не невозможно сделать это, но
это достаточно трудно, так что никто на самом деле не делает это.
Вместо этого каждый раз, когда пользователь выбирает "File/Save",
весь архив ZIP переписан. Следовательно, "File/Save"
занимает больше времени, чем должно, особенно на более старых аппаратных
средствах. Более новые машины быстрее, но это все еще проблема, когда
изменение отдельного символа в представлении на 50 мегабайтов пишет все
50 мегабайт заново на диск.
Запуск медленный.
В соответствии с темой груды файлов, OpenDocument хранит все содержание
в единственном большом XML-файле под названием "content.xml".
LibreOffice читает и разбирает этот весь файл только, чтобы показать первый
слайд. LibreOffice также читает все изображения в память,
когда пользователь выбирает "File/Save"
даже при том, что ни один из слайдов не изменен.
Результирующий эффект состоит в том, что запуск медленный. Двойной щелчок по
файлу OpenDocument поднимает индикатор выполнения, а не первый слайд.
Это приводит к плохому пользовательскому опыту. Ситуация становится еще более
раздражающей, когда размер документа увеличивается.
Больше памяти требуется.
Поскольку архивы ZIP оптимизированы для хранения больших кусков
содержания, они поощряют стиль программирования, где весь документ прочитан в
память при запуске, все редактирование происходит в памяти, тогда весь
документ записан во время "File/Save". OpenOffice и его потомки
охватывают этот образец.
Можно было бы утверждать, что нормально, в эту эру рабочих столов по
несколько мегабайт, читать весь документ в память. Но это не так.
Объем использованной памяти сильно превышает (сжатый) размер файла на диске.
Таким образом, слайд-шоу 50 МБ могло бы занять 200 МБ или больше RAM.
Это все еще не проблема, если один-единственный документ редактируется за
один раз. Но работая над разговором, у этого автора, как правило, будет 10
или 15 различных презентаций в то же время (чтобы облегчить
скопировать/вставить слайды от прошлой) и таким образом, гигабайты памяти
будут требоваться. Добавьте открытый веб-браузере или два и несколько других
настольных приложений, и внезапно машина тормозит.
Даже наличие только одного документа является проблемой, работая с недорогим
Chromebook, модифицированным с Ubuntu.
Использовать меньше памяти всегда лучше. Восстановление после сбоя.
Потомки OpenOffice склоняются к segfault чаще, чем коммерческие
конкуренты. Возможно, поэтому они
делают периодические резервные копии своих документов в памяти так, чтобы
пользователи не теряли данные, когда неизбежный сбой приложения действительно
происходит. Это вызывает расстраивающие паузы в течение нескольких секунд
в то время, как каждая резервная копия делается.
После перезапуска после сбоя пользователю дают диалоговое окно
процесса восстановления. Справляясь с восстановлением
этот путь включает большую дополнительную прикладную логику и обычно является
проблемой для пользователя.
Содержание недоступно.
Нельзя легко рассмотреть, изменить или извлечь содержание презентации
OpenDocument, используя универсальные инструменты. Единственный разумный
способ рассмотреть или отредактировать документ OpenDocument состоит в том,
чтобы открыть его в приложении, которое специально предназначено, чтобы
прочитать или написать OpenDocument (LibreOffice или нечто вроде).
Ситуация могла быть хуже. Можно извлечь и рассмотреть отдельные изображения,
используя просто инструмент "zip". Но это не разумная попытка извлечь текст
из презентации. Помните, что все содержание сохранено в единственном файле
"context.xml". Этот файл XML, таким образом, это текстовый файл. Но это не
текстовый файл, которым можно управлять обычным текстовым редактором.
Для презентации в качестве примера выше файл content.xml
состоит точно из двух строк. Первая:
Вторая содержит 211792 символов непроницаемого XML. Да, 211792 знака,
все на одной строке. Этот файл хороший тест напряжения текстового редактора.
К счастью, файл это не некоторый неясный двоичный формат, но с точки зрения
доступности, это очень так себе. Давайте предположим, что вместо того, чтобы использовать архив ZIP, чтобы
хранить его файлы, OpenDocument использовал очень простую базу данных SQLite
со следующей схемой единственной таблицы:
Для этого первого эксперимента ничто иное в формате файла не изменяется.
OpenDocument все еще груда файлов, только теперь каждый файл это строка в
базе данных SQLite, а не запись в архиве ZIP. Это простое изменение не
использует силу реляционной базы данных. Несмотря на это, это простое
изменение показывает некоторые улучшения.
Удивительно, использование SQLite вместо ZIP
делает файл презентации меньше. Действительно. Можно было бы подумать, что
файл реляционной базы данных будет больше, чем архив ZIP, но по крайней мере
в случае NeoOffice это не так.
Следующее показывает размеры той же самой презентации NeoOffice,
в его оригинальном формате архива ZIP, как произведено NeoOffice
(self2014.odp), и как база данных SQLite, используя утилиту
SQLAR:
Файл ("self2014.sqlar") базы данных SQLite приблизительно на половину
процента меньше, чем эквивалентный файл ODP! Как это может быть? По-видимому,
логика генератора архива ZIP в NeoOffice не так эффективна, как это могло
быть, потому что, когда та же самая груда файлов повторно сжата, используя
"zip", полученный файл ("zip.odp")
еще меньше, как замечено в третьей строке выше.
Так, хорошо написанный архив ZIP может быть немного меньшим, чем
эквивалентная база данных SQLite, как можно было бы ожидать.
Но различие небольшое. Так что что база данных SQLite конкурентоспособна по
размеру по отношению к архиву ZIP.
Другое преимущество для использования SQLite вместо ZIP состоит в том, что
документ может теперь быть обновлен без риска разрушения посреди обновления.
Помните, что записи в
БД SQLite атомные.
Правда, все содержание все еще сохранено в единственном большом XML-файле
("content.xml"), который должен быть полностью переписан, когда отдельный
символ изменяется. Но с SQLite только один файл должен измениться.
Другие 77 файлов в хранилище могут остаться неизменными. Они не должны быть
все переписаны при "File/Save".
Груда файлов поощряет содержание быть сохраненным в нескольких больших
кусках. В случае ODP есть всего четыре XML-файла, которые определяют
расположение всех слайдов. База данных SQLite позволяет хранить информацию в
нескольких больших кусках, но SQLite также эффективен при том, чтобы хранить
информацию в многочисленных мелких кусочках.
Таким образом, вместо того, чтобы хранить все содержание для всех слайдов
в единственном негабаритном XML-файле ("content.xml"),
предположите, что была отдельная таьблица для хранения содержания каждого
слайда отдельно. Схема могла бы выглядеть примерно так:
Содержание каждого слайда могло все еще быть сохранено, как сжатый XML.
Но теперь каждая страница сохранена отдельно.
Таким образом, открывая новый документ, приложение может просто выполнить:
Этот запрос быстро и эффективно возвратит содержание из первого слайда,
которое тогда может быть быстро разобрано и показано пользователю.
Только одна страница должна быть прочитана и разобрана в ходе показа
первого экрана, что означает, что первый экран кажется намного быстрее, и
больше нет потребности в раздражающем индикаторе выполнения.
Если бы приложение хотело держать все содержание в памяти, это могло бы
продолжить читать и разбирать другие страницы, используя фоновый поток после
рисования первой страницы.
Или, если чтение от SQLite достаточно эффективно, приложение вместо этого
могло бы уменьшить объем потребляемой памяти и держать только единственный
слайд в памяти за один раз. Или возможно это держит текущий слайд и следующий
в памяти как средство быстрого перехода к следующему.
Заметьте, что деление содержания на мелкие кусочки, используя таблицы
SQLite дает гибкость. Приложение может прочитать все содержание в память при
запуске. Или это может прочитать всего несколько страниц в память и держать
остальнын на диске. Или это может прочитать просто единственную страницу в
память за один раз. И различные версии могут сделать различный выбор, не имея
необходимости вносить изменения в формат файла. Такие варианты недоступны,
когда все содержание находится в единственном большом XML-файле в архиве ZIP.
Разделение содержания на мелкие кусочки также помогает ускорить File/Save.
Вместо того, чтобы иметь необходимость написать содержание всех страниц,
делая File/Save, приложение должно написать только те страницы, которые на
самом деле изменились.
Одна незначительная оборотная сторона разделения содержания на мелкие
кусочки: сжатие не работает так эффективно с более короткими текстами и таким
образом, размер документа мог бы увеличиться.
Но поскольку большая часть документа используется, чтобы сохранить
изображения, маленькое сокращение эффективности сжатия текстового содержания
будет едва заметно и является маленькой ценой за улучшение работы.
Как только каждый доволен понятием хранения каждого слайда
отдельно, следует еще маленький шаг, чтобы поддержать управление версиями.
Рассмотрите следующую схему:
В этой схеме вместо каждого слайда, имеющего номер страницы, который
определяет его порядок в рамках презентации, у каждого слайда
есть уникальный идентификатор, который не связан с тем, где это находится в
последовательности. Порядок слайдов в представлении определяется списком
slideIds, сохраненным как текстовая строка в колонке MANIFEST таблицы
VERSION. Так как многократные записи допустимы в таблице VERSION,
это означает, что многократные презентации
могут быть сохранены в том же самом документе.
При запуске сначала надо решить, какую версию приложение хочет показать.
Так как versionId естественно увеличится, и можно обычно хотеть
видеть последнюю версию, соответствующий запрос мог бы быть:
Или возможно приложение использовало бы новый checkinTime:
Используя единый запрос, как вышеупомянутый, приложение
получает список slideIds для всех слайдов в презентации. Приложение тогда
запрашивает содержание первого слайда, разбирает и показывает это
содержание, как прежде.
Да, тот второй запрос использует "max(checkinTime)". Он правда работает и
действительно дает четко определенный ответ в SQLite. Такой запрос дает
неопределенный ответ или производит ошибку во многих других БД SQL, но в
SQLite он делает то, что вы ожидали бы: это возвращает декларацию и versionId
записи, у которой есть максимум checkinTime.
Когда пользователь делает "File/Save",
вместо того, чтобы переписать измененные слайды, приложнение может теперь
сделать новые записи в таблице SLIDE для тех слайдов, которые были добавлены
или изменены. Тогда это создает новую запись в таблице VERSION,
содержащей пересмотренную декларацию.
Таблица VERSION есть колонки, чтобы сделать запись комментария
(по-видимому поставляемого пользователем), а также время и дату, в которое
произошло действие File/Save. Это также делает запись родительской версии,
чтобы сделать запись истории изменений. Возможно, декларация могла быть
сохранена как дельта от родительской версии, хотя, как правило, декларация
будет достаточно маленькой, так что хранение дельты могло бы быть большей
проблемой, чем это стоит. Таблица SLIDE также содержит столбец
derivedFrom который мог использоваться для кодирования дельты, если
определено, что сохранение содержания презентации как дельты от ее
предыдущей версии это стоящая оптимизация.
Таким образом с этим простым изменением, файл ODP теперь хранит не
только новые правки презентации, но и историю всех правок.
Пользователь обычно хотел бы видеть просто новый выпуск презентации, но при
желании, пользователь может теперь пойти назад, чтобы видеть исторические
версии той же самой презентации.
Или, многократные представления могли быть сохранены в рамках того
же самого документа.
С такой схемой приложение больше не должно было бы делать периодические
резервные копии изменений отдельного файла, чтобы избежать потерянной работы
в случае катастрофы. Вместо этого специальная версия могла быть назначена и
не сохранена, изменения могли быть написаны в нее. Поскольку только изменения
должны были бы быть написаны, а не весь документ, сохранение
изменения включит только написание нескольких килобайтов содержания, а не
многих мегабайт, и заняло бы миллисекунды вместо секунд,
таким образом, это могло делаться часто и тихо в фоновом режиме.
Тогда, когда катастрофа происходит, все (или почти все) работы сохраняются.
Если пользователь решает отменить несохраненные
изменения, они просто возвращаются к предыдущей версии.
Есть детали, чтобы заполнить здесь. Возможно, может быть предоставлена
история изменений (возможно, с графом) давая разрешение пользователю выбрать,
какую версию он хочет смотреть или отредактировать.
Возможно, некоторая услуга может быть предоставлена, чтобы слить форки,
которые могли бы произойти в истории версий. Возможно приложение должно
обеспечить средство произвести чистку старых и нежелательных версий.
Ключевой пункт то, что использование базы данных SQLite, чтобы сохранить
содержание, а не архив ZIP, делает все эти особенности
намного легче осуществимыми.
В предыдущих секциях мы видели, как переход от хранилища key/value,
осуществленного как ZIP-архив, к простой базе данных SQLite всего с тремя
таблицами может добавить значительные возможности к формату файла приложения.
Мы могли продолжить увеличивать схему с новыми таблицами с индексами,
добавленными для работы, с триггерами и обзорами для программирования
удобства и ограничений, чтобы провести в жизнь последовательность содержания
даже перед лицом программных ошибок. Дальнейшие идеи улучшения включают:
У базы данных SQLite есть большая способность, которую это эссе только
начало затрагивать. Но надо надеяться этот быстрый обзор убедил некоторых
читателей, что использовать базу данных SQL как формат
файла приложения стоит.
Некоторые читатели могли бы сопротивляться использованию SQLite как формат
файла приложения из-за предшествующего воздействия баз данных SQL предприятия
и ограничений других систем. Например, много ядер базы данных предприятия
отговаривают от хранения больших последовательностей или BLOB в базе данных и
вместо этого предлагают, чтобы большие последовательности и BLOB были
сохранены как отдельные файлы, а имя файла сохранено в базе данных.
Но SQLite не похож на это. Любая колонка базы данных SQLite может содержать
последовательность или BLOB до приблизительно гигабайта в размере.
Для последовательностей и BLOB в 100 килобайтов или меньше
работа I/O лучше,
чем использование отдельных файлов.
Некоторые читатели могли бы отказаться рассмотреть SQLite как формат файла
приложения, потому что они знакомы идеей, что все схемы базы данных SQL
должны быть включены в третью нормальную форму и хранить только маленькие
примитивные типы данных, такие как последовательности и целые числа.
Конечно, относительная теория важна, и проектировщики должны стремиться
понять ее. Но, как продемонстрировано выше, часто приемлемо хранить сложную
информацию как XML или JSON в текстовых полях базы данных.
Таким образом, итог этого эссе состоит в том, что использование SQLite как
контейнера для формата файла приложения как OpenDocument и хранение большого
количества меньших объектов в том контейнере удаются намного лучше, чем
использование архива ZIP, содержащего несколько больших объектов.
Файл базы данных SQLite имеет приблизительно тот же самый размер,
а в некоторых случаях меньший, чем архив ZIP, содержащий ту
же самую информацию.
Атомные возможности обновления
SQLite позволяют небольшим возрастающим изменениям быть безопасно написанными
в документ. Это уменьшает полный I/O диска и улучшает работу File/Save.
Время запуска уменьшается, позволяя читать только содержание для
начального экрана. Это в основном избавляет от необходимости показывать
индикатор выполнения, открывая новый документ.
Документ просто немедленно появляется.
Объем потребляемой памяти применения может быть существенно уменьшен,
загрузив только содержание, которое относится к текущему показу, храня
большую часть содержания на диске. Быстрые запросы SQLite делают это
жизнеспособной альтернативой хранению всего содержания в памяти в любом
случае. И когда приложение используют меньше памяти, оно делает весь
компьютер более отзывчивым.
Схема базы данных SQL в состоянии представлять информацию более
непосредственно и кратко, чем база данных key/value,
такая как архив ZIP. Это делает содержание документа более доступным для
сторонних приложений и сценариев и облегчает расширенные функции, такие как
встроенное управление версиями документа и экономия работы для
восстановления после катастрофы. Это всего несколько из выгод
использования SQLite как формат файла приложения,
преимущества, которые кажутся наиболее вероятными, чтобы улучшить
пользовательский опыт для приложений вроде OpenOffice.
Другие программы могли бы извлечь выгоду из SQLite иначе. См. документ
Формата файла приложения
для дополнительных идей.
Наконец, давайте повторим, что это эссе мысленный эксперимент.
Формат OpenDocument известен и уже хорошо разработаны. Никто действительно не
полагает, что OpenDocument должен быть изменен, чтобы использовать SQLite в
качестве его контейнера вместо ZIP.
И при этом эта статья не критика OpenDocument для того, чтобы не выбирать
SQLite в качестве его контейнера, так как OpenDocument предшествует SQLite.
Скорее пункт этой статьи должен использовать OpenDocument в качестве
конкретного примера того, как SQLite может использоваться, чтобы построить
лучшие форматы файла приложения для будущих проектов.
Choose any three.
Что, если OpenDocument используется SQLite?
Введение
OpenDocument и OpenDocument Presentation
Archive: self2014.odp
Length Date Time Name
--------- ---------- ----- ----
47 2014-06-21 12:34 mimetype
0 2014-06-21 12:34 Configurations2/statusbar/
0 2014-06-21 12:34 Configurations2/accelerator/current.xml
0 2014-06-21 12:34 Configurations2/floater/
0 2014-06-21 12:34 Configurations2/popupmenu/
0 2014-06-21 12:34 Configurations2/progressbar/
0 2014-06-21 12:34 Configurations2/menubar/
0 2014-06-21 12:34 Configurations2/toolbar/
0 2014-06-21 12:34 Configurations2/images/Bitmaps/
54702 2014-06-21 12:34 Pictures/10000000000001F40000018C595A5A3D.png
46269 2014-06-21 12:34 Pictures/100000000000012C000000A8ED96BFD9.png
... 58 other pictures omitted...
13013 2014-06-21 12:34 Pictures/10000000000000EE0000004765E03BA8.png
1005059 2014-06-21 12:34 Pictures/10000000000004760000034223EACEFD.png
211831 2014-06-21 12:34 content.xml
46169 2014-06-21 12:34 styles.xml
1001 2014-06-21 12:34 meta.xml
9291 2014-06-21 12:34 Thumbnails/thumbnail.png
38705 2014-06-21 12:34 Thumbnails/thumbnail.pdf
9664 2014-06-21 12:34 settings.xml
9704 2014-06-21 12:34 META-INF/manifest.xml
--------- -------
10961006 78 files
application/vnd.oasis.opendocument.presentation
Ограничения формата презентации OpenDocument
<?xml version="1.0" encoding="UTF-8"?>
Первое улучшение: замените ZIP на SQLite
CREATE TABLE OpenDocTree(
filename TEXT PRIMARY KEY, -- Name of file
filesize BIGINT, -- Size of file after decompression
content BLOB -- Compressed file content
);
-rw-r--r-- 1 drh staff 10514994 Jun 8 14:32 self2014.odp
-rw-r--r-- 1 drh staff 10464256 Jun 8 14:37 self2014.sqlar
-rw-r--r-- 1 drh staff 10416644 Jun 8 14:40 zip.odp
Второе улучшение: содержание разделено на мелкие кусочки
CREATE TABLE slide(
pageNumber INTEGER, -- The slide page number
slideContent TEXT -- Slide content as XML or JSON
);
CREATE INDEX slide_pgnum ON slide(pageNumber); -- Optional
SELECT slideContent FROM slide WHERE pageNumber=1;
Третье улучшение: управление версиями
CREATE TABLE slide(
slideId INTEGER PRIMARY KEY,
derivedFrom INTEGER REFERENCES slide,
content TEXT -- XML or JSON or whatever
);
CREATE TABLE version(
versionId INTEGER PRIMARY KEY,
priorVersion INTEGER REFERENCES version,
checkinTime DATETIME, -- When this version was saved
comment TEXT, -- Description of this version
manifest TEXT -- List of integer slideIds
);
SELECT manifest, versionId FROM version ORDER BY versionId DESC LIMIT 1;
SELECT manifest, versionId, max(checkinTime) FROM version;
Еще кое-что
Обзор выгоды использования SQLite