Статьи

LevelDB в рубине

LevelDB

Обычно, когда вы отправляетесь с приложением Rails, ваши данные живут в базе данных MySQL (или PostgreSQL, или SQLite, или Oracle). Но оказывается, что традиционная реляционная база данных не очень подходит для всех типов данных. Например, если вы хотите очень быстрый доступ к данным, которые не часто записываются на диск, вы можете хранить эту информацию в оперативной памяти. В этом случае вам может понадобиться Redis или Memcached. Если ваши данные могут быть представлены в виде графика, вы можете проверить Neo4j или OrientDB. Но иногда вам не нужен или не нужен полноценный сервер баз данных; вместо этого вы можете обойтись простой библиотекой, которая может быть упакована вместе с вашим приложением. Вот где LevelDB подходит: это библиотека хранения значений ключей, которая должна быть быстрой.

В этой статье мы рассмотрим, как использовать LevelDB с оберткой Ruby, а также обсудим, где следует и не следует использовать LevelDB, представив общие примеры использования.

Почему не хэш?

LevelDB — это библиотека хранения значений ключей, то есть вы можете связать строковые ключи и строковые значения, к которым можно будет обратиться позже. Подождите секунду — это не похоже на хэш Ruby? Какой смысл добавлять другую зависимость, если у Ruby уже есть то, что мы хотим? Оказывается, что хэш Ruby нельзя использовать как хранилище значений ключей на диске.

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

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

LevelDB дает нам некоторую автоматическую синхронизацию. Если у нас есть два потока, обращающихся к одной и той же базе данных, для некоторых операций LevelDB позаботится о том, чтобы мы не столкнулись с проблемой, пытаясь получить доступ к базе данных или изменить ее одновременно. Наконец, есть и другие преимущества в производительности, которые дает нам LevelDB.

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

LevelDB-Ruby Основы

API «по умолчанию» для LevelDB написан на C ++, но, к счастью, кто-то написал нам, Rubyists, привязку! Прежде чем мы сможем установить его, нам нужна копия нативной библиотеки. Установка зависит от того, на какой платформе вы находитесь (или вы можете просто скомпилировать из исходного кода). Если вы работаете в Mac OS X с Homebrew, вы можете запустить:

brew install leveldb

Если вы используете производную от Debian систему (например, Ubuntu):

 sudo apt-get install leveldb

Теперь мы можем установить драгоценный камень:

 gem install leveldb-ruby

К сожалению, этот драгоценный камень немного стар и ему не хватает нескольких функций, но, тем не менее, он довольно полезен. Давайте сразу перейдем к простому примеру:

 require 'leveldb'

db = LevelDB::DB.new("my-database.db")
db.put "dhaivat", "pandya"

Сначала создайте базу данных LevelDB (которая на самом деле создается как каталог с кучей файлов внутри) и свяжите ключ «dhaivat» со значением «pandya». Мы можем получить значения из «хеша» так же легко:

 db.get "dhaivat"

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

 db["dhaivat"] = "pandya"
p db["dhaivat"]

Мы также получаем довольно полезный служебный метод с названием contains? это говорит нам, содержит ли база данных данный ключ:

 db.includes?("dhaivat") => true

Мы можем получить ключи и значения так же легко:

 db.keys
db.values

итерация

Под капотом LevelDB можно представить как очень эффективную реализацию структуры данных, называемой «B + Tree». Это означает, что хэш, который представляет LevelDB, является упорядоченным . Другими словами, каждая пара ключ-значение сортируется в соответствии с определенным правилом, тогда как Ruby Hash не дает такой гарантии упорядочения. По умолчанию LevelDB упорядочивает пары в алфавитном порядке, поэтому мы можем перебирать их в следующем порядке:

 db.each do |key, value|
  puts "#{key}, #{value}"
end

Мы можем даже отобразить базу данных так же, как и для обычного хэша Ruby:

 db.map do |key, value|
  [key, " #{value} "]
end

валентность

До сих пор мы просто связывали воедино несколько довольно простых операций, не задумываясь о том, что произойдет, если одна из них потерпит неудачу. Давайте посмотрим на этот сценарий:

 db.put "fred", "smith"
db.put "john", db["fred"]
db.delete "fred"

Что, если мы как-то потерпим неудачу непосредственно перед удалением «fred»? Это означало бы, что значение «кузнец» теперь ассоциируется как с «Фредом», так и с «Джоном». Во многих случаях такой посреднический случай может разрушить вашу бизнес-логику. Вместо этого нам нужен способ убедиться, что либо все три операции завершены, либо ни одна из них не выполнена. Нам нужна атомарность.

LevelDB предоставляет концепцию «WriteBatch» для этого. Возьмите все операции и поместите их в WriteBatch. Как правило, они выполняются как атомарная операция. Вот пример:

 db.batch do |b|
  b.put "fred", "smith"
  b.put "john", b['fred']
  b.delete "fred"
end

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

 db.batch, :sync => true do |b|
  b.put "fred", "smith"
  b.put "john", b['fred']
  b.delete "fred"       
end

Случаи использования

Хорошо, теперь у нас есть базовый дескриптор LevelDB. Это кажется слишком простым, но вот где LevelDB сияет. Он скрывает все сложные оптимизации, алгоритмы и тому подобное, чтобы создать впечатление, будто вы просто получаете доступ к странному маленькому Ruby Hash.

Но когда вы должны его использовать?

Первое, что нужно ясно понять, это то, что LevelDB не является сервером базы данных. Там нет вовлеченного сервера. Это просто библиотека. Это дает ему довольно огромное преимущество: вам не нужно знать о настройке цели развертывания, чтобы эффективно обрабатывать данные на диске. Итак, если вы пишете приложение, работающее в среде, в которой, возможно, еще нет готовых к использованию Postgres или MySQL, LevelDB определенно стоит рассмотреть. Например, LevelDB часто используется на стороне клиента с Javascript, потому что нет хорошего эквивалента реляционной базы данных.

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

Завершение

Надеюсь, вам понравился этот тур по LevelDB через Ruby. Если у вас есть какие-либо вопросы, оставьте их в комментариях ниже!