RussianLDP Рейтинг@Mail.ru
WebMoney: 
WMZ Z294115950220 
WMR R409981405661 
WME E134003968233 
Visa 
4274 3200 2453 6495 

Small. Fast. Reliable.
Choose any three.

Что, если OpenDocument используется SQLite?

Введение

Предположим, что формат файла OpenDocument и формат презентации "ODP" OpenDocument были бы построены вокруг SQLite. Преимущества включали бы:

  • Меньшие документы
  • Ускорение File/Save
  • Более быстрый запуск
  • Меньше памяти используется
  • Управление версиями документа
  • Лучший пользовательский опыт

Обратите внимание на то, что это только мысленный эксперимент. Мы не предлагаем, чтобы OpenDocument был изменен. И при этом эта статья не критика текущего дизайна OpenDocument. Пункт этого эссе должен предложить способы улучшить будущие проекты формата файла.

OpenDocument и OpenDocument Presentation

Формат файла OpenDocument используется для офисных приложений: текстовые процессоры, электронные таблицы и представления. Это было первоначально разработано для OpenOffice, но было с тех пор включено в другие приложения. Например, в NeoOffice на Mac или в LibreOffice в Linux и Windows.

OpenDocument Presentation или "ODP" это ZIP-архив, содержащий XML-файлы, описывающие слайды презентации и отдельные файлы изображений для различных изображений, которые включены как часть презентации. Текстовой процессор OpenDocument и файлы электронной таблицы так же структурированы, но не рассматриваются в этой статье. Читатель может легко видеть содержание файла ODP при помощи вывода "zip -l" Например, следующее вывод из 49 слайдовых презентаций о SQLite с 2014 SouthEast LinuxFest :

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

ODP ZIP включает четыре различных XML-файла: content.xml, styles.xml, meta.xml и settings.xml. Те четыре файла определяют расположение слайда, текстовое содержание и моделирование. Это конкретное представление содержит 62 изображения, в пределах от полноэкранных картин до символов, каждое сохранено как отдельный файл в папке Pictures. Файл "mimetype" содержит единственную строку текста, в которой говорится:

application/vnd.oasis.opendocument.presentation

Цель других файлов и папок в настоящее время неизвестна, но вероятно это не трудно выяснить.

Ограничения формата презентации OpenDocument

Использование архива ZIP, чтобы заключить в капсулу XML-файлы плюс ресурсы является изящным подходом к формату файла приложения. Это ясно превосходит формат двоичного файла. Но использование базы данных SQLite как контейнера вместо ZIP, было бы более изящным.

ZIP-архив в основном база данных key/value, оптимизированная для случая write-once/read-many и для относительно небольшого количества различных ключей (от нескольких сотен до тысяч) каждый с большим BLOB как значение. Архив ZIP может быть рассмотрен как база данных "груды файлов". Это работает, но у этого есть некоторые недостатки относительно базы данных SQLite:

  1. Инкрементное обновление трудно.

    Трудно обновить отдельные записи в архиве ZIP. Особенно трудно обновить отдельные записи в архиве ZIP способом, который не уничтожает весь документ, если компьютер выключается посреди обновления. Не невозможно сделать это, но это достаточно трудно, так что никто на самом деле не делает это. Вместо этого каждый раз, когда пользователь выбирает "File/Save", весь архив ZIP переписан. Следовательно, "File/Save" занимает больше времени, чем должно, особенно на более старых аппаратных средствах. Более новые машины быстрее, но это все еще проблема, когда изменение отдельного символа в представлении на 50 мегабайтов пишет все 50 мегабайт заново на диск.

  2. Запуск медленный.

    В соответствии с темой груды файлов, OpenDocument хранит все содержание в единственном большом XML-файле под названием "content.xml". LibreOffice читает и разбирает этот весь файл только, чтобы показать первый слайд. LibreOffice также читает все изображения в память, когда пользователь выбирает "File/Save" даже при том, что ни один из слайдов не изменен. Результирующий эффект состоит в том, что запуск медленный. Двойной щелчок по файлу OpenDocument поднимает индикатор выполнения, а не первый слайд. Это приводит к плохому пользовательскому опыту. Ситуация становится еще более раздражающей, когда размер документа увеличивается.

  3. Больше памяти требуется.

    Поскольку архивы ZIP оптимизированы для хранения больших кусков содержания, они поощряют стиль программирования, где весь документ прочитан в память при запуске, все редактирование происходит в памяти, тогда весь документ записан во время "File/Save". OpenOffice и его потомки охватывают этот образец.

    Можно было бы утверждать, что нормально, в эту эру рабочих столов по несколько мегабайт, читать весь документ в память. Но это не так. Объем использованной памяти сильно превышает (сжатый) размер файла на диске. Таким образом, слайд-шоу 50 МБ могло бы занять 200 МБ или больше RAM. Это все еще не проблема, если один-единственный документ редактируется за один раз. Но работая над разговором, у этого автора, как правило, будет 10 или 15 различных презентаций в то же время (чтобы облегчить скопировать/вставить слайды от прошлой) и таким образом, гигабайты памяти будут требоваться. Добавьте открытый веб-браузере или два и несколько других настольных приложений, и внезапно машина тормозит. Даже наличие только одного документа является проблемой, работая с недорогим Chromebook, модифицированным с Ubuntu. Использовать меньше памяти всегда лучше.

  4. Восстановление после сбоя.

    Потомки OpenOffice склоняются к segfault чаще, чем коммерческие конкуренты. Возможно, поэтому они делают периодические резервные копии своих документов в памяти так, чтобы пользователи не теряли данные, когда неизбежный сбой приложения действительно происходит. Это вызывает расстраивающие паузы в течение нескольких секунд в то время, как каждая резервная копия делается. После перезапуска после сбоя пользователю дают диалоговое окно процесса восстановления. Справляясь с восстановлением этот путь включает большую дополнительную прикладную логику и обычно является проблемой для пользователя.

  5. Содержание недоступно.

    Нельзя легко рассмотреть, изменить или извлечь содержание презентации OpenDocument, используя универсальные инструменты. Единственный разумный способ рассмотреть или отредактировать документ OpenDocument состоит в том, чтобы открыть его в приложении, которое специально предназначено, чтобы прочитать или написать OpenDocument (LibreOffice или нечто вроде). Ситуация могла быть хуже. Можно извлечь и рассмотреть отдельные изображения, используя просто инструмент "zip". Но это не разумная попытка извлечь текст из презентации. Помните, что все содержание сохранено в единственном файле "context.xml". Этот файл XML, таким образом, это текстовый файл. Но это не текстовый файл, которым можно управлять обычным текстовым редактором. Для презентации в качестве примера выше файл content.xml состоит точно из двух строк. Первая:

    <?xml version="1.0" encoding="UTF-8"?>
    

    Вторая содержит 211792 символов непроницаемого XML. Да, 211792 знака, все на одной строке. Этот файл хороший тест напряжения текстового редактора. К счастью, файл это не некоторый неясный двоичный формат, но с точки зрения доступности, это очень так себе.

Первое улучшение: замените ZIP на SQLite

Давайте предположим, что вместо того, чтобы использовать архив ZIP, чтобы хранить его файлы, OpenDocument использовал очень простую базу данных SQLite со следующей схемой единственной таблицы:

CREATE TABLE OpenDocTree(
  filename TEXT PRIMARY KEY,  -- Name of file
  filesize BIGINT,            -- Size of file after decompression
  content BLOB                -- Compressed file content
);

Для этого первого эксперимента ничто иное в формате файла не изменяется. OpenDocument все еще груда файлов, только теперь каждый файл это строка в базе данных SQLite, а не запись в архиве ZIP. Это простое изменение не использует силу реляционной базы данных. Несмотря на это, это простое изменение показывает некоторые улучшения.

Удивительно, использование SQLite вместо ZIP делает файл презентации меньше. Действительно. Можно было бы подумать, что файл реляционной базы данных будет больше, чем архив ZIP, но по крайней мере в случае NeoOffice это не так. Следующее показывает размеры той же самой презентации NeoOffice, в его оригинальном формате архива ZIP, как произведено NeoOffice (self2014.odp), и как база данных SQLite, используя утилиту SQLAR:

-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

Файл ("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"), предположите, что была отдельная таьблица для хранения содержания каждого слайда отдельно. Схема могла бы выглядеть примерно так:

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

Содержание каждого слайда могло все еще быть сохранено, как сжатый XML. Но теперь каждая страница сохранена отдельно. Таким образом, открывая новый документ, приложение может просто выполнить:

SELECT slideContent FROM slide WHERE pageNumber=1;

Этот запрос быстро и эффективно возвратит содержание из первого слайда, которое тогда может быть быстро разобрано и показано пользователю. Только одна страница должна быть прочитана и разобрана в ходе показа первого экрана, что означает, что первый экран кажется намного быстрее, и больше нет потребности в раздражающем индикаторе выполнения.

Если бы приложение хотело держать все содержание в памяти, это могло бы продолжить читать и разбирать другие страницы, используя фоновый поток после рисования первой страницы. Или, если чтение от SQLite достаточно эффективно, приложение вместо этого могло бы уменьшить объем потребляемой памяти и держать только единственный слайд в памяти за один раз. Или возможно это держит текущий слайд и следующий в памяти как средство быстрого перехода к следующему.

Заметьте, что деление содержания на мелкие кусочки, используя таблицы SQLite дает гибкость. Приложение может прочитать все содержание в память при запуске. Или это может прочитать всего несколько страниц в память и держать остальнын на диске. Или это может прочитать просто единственную страницу в память за один раз. И различные версии могут сделать различный выбор, не имея необходимости вносить изменения в формат файла. Такие варианты недоступны, когда все содержание находится в единственном большом XML-файле в архиве ZIP.

Разделение содержания на мелкие кусочки также помогает ускорить File/Save. Вместо того, чтобы иметь необходимость написать содержание всех страниц, делая File/Save, приложение должно написать только те страницы, которые на самом деле изменились.

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

Третье улучшение: управление версиями

Как только каждый доволен понятием хранения каждого слайда отдельно, следует еще маленький шаг, чтобы поддержать управление версиями. Рассмотрите следующую схему:

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

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

При запуске сначала надо решить, какую версию приложение хочет показать. Так как versionId естественно увеличится, и можно обычно хотеть видеть последнюю версию, соответствующий запрос мог бы быть:

SELECT manifest, versionId FROM version ORDER BY versionId DESC LIMIT 1;

Или возможно приложение использовало бы новый checkinTime:

SELECT manifest, versionId, max(checkinTime) FROM version;

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

Да, тот второй запрос использует "max(checkinTime)". Он правда работает и действительно дает четко определенный ответ в SQLite. Такой запрос дает неопределенный ответ или производит ошибку во многих других БД SQL, но в SQLite он делает то, что вы ожидали бы: это возвращает декларацию и versionId записи, у которой есть максимум checkinTime.

Когда пользователь делает "File/Save", вместо того, чтобы переписать измененные слайды, приложнение может теперь сделать новые записи в таблице SLIDE для тех слайдов, которые были добавлены или изменены. Тогда это создает новую запись в таблице VERSION, содержащей пересмотренную декларацию.

Таблица VERSION есть колонки, чтобы сделать запись комментария (по-видимому поставляемого пользователем), а также время и дату, в которое произошло действие File/Save. Это также делает запись родительской версии, чтобы сделать запись истории изменений. Возможно, декларация могла быть сохранена как дельта от родительской версии, хотя, как правило, декларация будет достаточно маленькой, так что хранение дельты могло бы быть большей проблемой, чем это стоит. Таблица SLIDE также содержит столбец derivedFrom который мог использоваться для кодирования дельты, если определено, что сохранение содержания презентации как дельты от ее предыдущей версии это стоящая оптимизация.

Таким образом с этим простым изменением, файл ODP теперь хранит не только новые правки презентации, но и историю всех правок. Пользователь обычно хотел бы видеть просто новый выпуск презентации, но при желании, пользователь может теперь пойти назад, чтобы видеть исторические версии той же самой презентации.

Или, многократные представления могли быть сохранены в рамках того же самого документа.

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

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

Еще кое-что

В предыдущих секциях мы видели, как переход от хранилища key/value, осуществленного как ZIP-архив, к простой базе данных SQLite всего с тремя таблицами может добавить значительные возможности к формату файла приложения. Мы могли продолжить увеличивать схему с новыми таблицами с индексами, добавленными для работы, с триггерами и обзорами для программирования удобства и ограничений, чтобы провести в жизнь последовательность содержания даже перед лицом программных ошибок. Дальнейшие идеи улучшения включают:

  • Хранение как автоматический стек undo/redo в таблице базы данных так, чтобы Undo мог возвратить в предшествующие сеансы редактирования.
  • Добавку полнотекстового поиска.
  • Анализируйте файл "settings.xml" в таблице SQL, которая более легко рассматривается и редактируется отдельными приложениями.
  • Вынесите "Presentor Notes" от каждого слайда в отдельную таблицу для более легкого доступа от сторонних запросов и/или сценариев.
  • Увеличьте понятие представления вне простой линейной последовательности слайдов, чтобы позволить запасные пути и экскурсии в зависимости от того, как аудитория отвечает.

У базы данных SQLite есть большая способность, которую это эссе только начало затрагивать. Но надо надеяться этот быстрый обзор убедил некоторых читателей, что использовать базу данных SQL как формат файла приложения стоит.

Некоторые читатели могли бы сопротивляться использованию SQLite как формат файла приложения из-за предшествующего воздействия баз данных SQL предприятия и ограничений других систем. Например, много ядер базы данных предприятия отговаривают от хранения больших последовательностей или BLOB в базе данных и вместо этого предлагают, чтобы большие последовательности и BLOB были сохранены как отдельные файлы, а имя файла сохранено в базе данных. Но SQLite не похож на это. Любая колонка базы данных SQLite может содержать последовательность или BLOB до приблизительно гигабайта в размере. Для последовательностей и BLOB в 100 килобайтов или меньше работа I/O лучше, чем использование отдельных файлов.

Некоторые читатели могли бы отказаться рассмотреть SQLite как формат файла приложения, потому что они знакомы идеей, что все схемы базы данных SQL должны быть включены в третью нормальную форму и хранить только маленькие примитивные типы данных, такие как последовательности и целые числа. Конечно, относительная теория важна, и проектировщики должны стремиться понять ее. Но, как продемонстрировано выше, часто приемлемо хранить сложную информацию как XML или JSON в текстовых полях базы данных.

Обзор выгоды использования SQLite

Таким образом, итог этого эссе состоит в том, что использование SQLite как контейнера для формата файла приложения как OpenDocument и хранение большого количества меньших объектов в том контейнере удаются намного лучше, чем использование архива ZIP, содержащего несколько больших объектов.

  1. Файл базы данных SQLite имеет приблизительно тот же самый размер, а в некоторых случаях меньший, чем архив ZIP, содержащий ту же самую информацию.

  2. Атомные возможности обновления SQLite позволяют небольшим возрастающим изменениям быть безопасно написанными в документ. Это уменьшает полный I/O диска и улучшает работу File/Save.

  3. Время запуска уменьшается, позволяя читать только содержание для начального экрана. Это в основном избавляет от необходимости показывать индикатор выполнения, открывая новый документ. Документ просто немедленно появляется.

  4. Объем потребляемой памяти применения может быть существенно уменьшен, загрузив только содержание, которое относится к текущему показу, храня большую часть содержания на диске. Быстрые запросы SQLite делают это жизнеспособной альтернативой хранению всего содержания в памяти в любом случае. И когда приложение используют меньше памяти, оно делает весь компьютер более отзывчивым.

  5. Схема базы данных SQL в состоянии представлять информацию более непосредственно и кратко, чем база данных key/value, такая как архив ZIP. Это делает содержание документа более доступным для сторонних приложений и сценариев и облегчает расширенные функции, такие как встроенное управление версиями документа и экономия работы для восстановления после катастрофы.

Это всего несколько из выгод использования SQLite как формат файла приложения, преимущества, которые кажутся наиболее вероятными, чтобы улучшить пользовательский опыт для приложений вроде OpenOffice. Другие программы могли бы извлечь выгоду из SQLite иначе. См. документ Формата файла приложения для дополнительных идей.

Наконец, давайте повторим, что это эссе мысленный эксперимент. Формат OpenDocument известен и уже хорошо разработаны. Никто действительно не полагает, что OpenDocument должен быть изменен, чтобы использовать SQLite в качестве его контейнера вместо ZIP. И при этом эта статья не критика OpenDocument для того, чтобы не выбирать SQLite в качестве его контейнера, так как OpenDocument предшествует SQLite. Скорее пункт этой статьи должен использовать OpenDocument в качестве конкретного примера того, как SQLite может использоваться, чтобы построить лучшие форматы файла приложения для будущих проектов.