Spec-Zone.ru › SQLite

Что, если OpenDocument использовал SQLite?

Введение

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

  • Меньшие документы
  • Более быстрое время открытия/сохранения файлов
  • Более быстрое время запуска
  • Меньшее использование памяти
  • Версионирование документов
  • Более удобный пользовательский интерфейс

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

О OpenDocument и OpenDocument Presentation

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

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

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

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

application/vnd.oasis.opendocument.presentation

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

Ограничения формата OpenDocument Presentation

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

ZIP-архив — это по сути база данных ключ/значение, оптимизированная для однократного записи/многократного чтения и для сравнительно небольшого количества различных ключей (от нескольких сотен до нескольких тысяч), каждый из которых имеет большое значение BLOB. ZIP-архив можно рассматривать как базу данных «стопка-файлов». Это работает, но у него есть некоторые недостатки по сравнению с базой данных SQLite, а именно:

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

    Трудно обновлять отдельные записи в ZIP-архиве. Особенно трудно обновлять отдельные записи в ZIP-архиве таким образом, чтобы не разрушить весь документ, если компьютер теряет питание и/или зависает во время обновления. Это не невозможно, но достаточно сложно, чтобы никто этого на самом деле не делал. Вместо этого, всякий раз, когда пользователь выбирает «Файл/Сохранить», весь ZIP-архив перезаписывается. Следовательно, «Файл/Сохранить» занимает больше времени, чем следовало бы, особенно на старом оборудовании. Более новые машины работают быстрее, но всё же раздражает, что изменение одного символа в презентации размером 50 мегабайт приводит к расходованию 50 мегабайт конечного ресурса записи на SSD.

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

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

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

    Поскольку ZIP-архивы оптимизированы для хранения больших блоков содержимого, они поощряют стиль программирования, при котором весь документ считывается в память при запуске, все изменения происходят в памяти, а затем весь документ записывается на диск во время «Файл/Сохранить». OpenOffice и его потомки поддерживают эту схему.

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

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

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

  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

Файл базы данных SQLite («self2014.sqlar») примерно на полпроцента меньше, чем эквивалентный файл ODP! Как это возможно? По-видимому, логика генератора ZIP-архивов в NeoOffice не так эффективна, как могла бы быть, потому что при пересжатии той же «стопки-файлов» с помощью командной строки «zip» получается файл («zip.odp»), который ещё меньше, ещё на полпроцента, как показано в третьей строке выше. Таким образом, хорошо написанный ZIP-архив может быть немного меньше, чем эквивалентная база данных SQLite, как можно было бы ожидать. Но разница незначительна. Ключевой вывод заключается в том, что база данных SQLite по размеру сопоставима с ZIP-архивом.

Другое преимущество использования SQLite вместо ZIP заключается в том, что документ можно обновлять инкрементально, не рискуя повреждением документа при отключении питания или другом сбое в середине обновления. (Помните, что записи в базах данных SQLite атомарны.) Да, всё содержимое по-прежнему хранится в одном большом XML-файле («content.xml»), который необходимо полностью переписать, даже если изменится один символ. Но с SQLite нужно изменить только этот один файл. Остальные 77 файлов в репозитории могут оставаться неизменными. Их не нужно все перезаписывать, что, в свою очередь, делает «Файл/Сохранить» гораздо быстрее и экономит износ SSD.

Второе улучшение: разделение содержимого на меньшие части

«Стопка-файлов» стимулирует хранение содержимого в нескольких больших блоках. В случае 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.

Разделение содержимого на меньшие части также помогает операциям «Файл/Сохранить» выполняться быстрее. Вместо того, чтобы переписывать содержимое всех страниц при выполнении операции «Файл/Сохранить», приложение должно переписывать только те страницы, которые фактически были изменены.

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

Третье улучшение: Версионирование

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

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

Когда пользователь выполняет операцию «Файл/Сохранить», вместо перезаписи измененных слайдов приложение может создавать новые записи в таблице SLIDE только для тех слайдов, которые были добавлены или изменены. Затем оно создает новую запись в таблице VERSION, содержащую переработанный манифест.

Таблица VERSION, показанная выше, имеет столбцы для записи комментария к загрузке (предположительно, введённого пользователем), времени и даты выполнения операции «Файл/Сохранить». Она также записывает родительскую версию для регистрации истории изменений. Возможно, манифест можно было бы хранить как разность по отношению к родительской версии, хотя обычно манифест достаточно мал, и хранение разности может быть более сложным, чем того стоит. Таблица SLIDE также содержит столбец derivedFrom, который можно использовать для кодирования разности, если считается, что сохранение содержимого слайда как разности по отношению к его предыдущей версии является целесообразной оптимизацией.

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

Или несколько презентаций могут храниться в одном документе.

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

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

И так далее…

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

  • Хранить автоматический стек отмены/повторной отмены в таблице базы данных, чтобы отмена могла возвращаться к предыдущим сессиям редактирования.
  • Добавить возможности полнотекстового поиска в набор слайдов или по нескольким наборам слайдов.
  • Разделить файл «settings.xml» на таблицу SQL, которую проще просматривать и редактировать с помощью отдельных приложений.
  • Выделить «Заметки докладчика» из каждого слайда в отдельную таблицу для более простого доступа из сторонних приложений и/или скриптов.
  • Расширить концепцию презентации за рамки простой линейной последовательности слайдов, чтобы позволить отклонения и экскурсии в зависимости от реакции аудитории.

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

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

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

Обзор преимуществ использования SQLite

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

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

  2. Функции атомарного обновления SQLite позволяют безопасно записывать небольшие инкрементные изменения в документ. Это снижает общее количество операций ввода-вывода на диск и улучшает производительность операции «Файл/Сохранить», повышая пользовательский опыт.

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

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

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

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

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

Эта страница в последний раз была изменена 10 октября 2023 г. 17:29:48 по Гринвичу

SQLite is in the Public Domain.
https://sqlite.org/affcase1.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API