Технологии Facebook: Cassandra

Продолжаю публиковать материалы, связанные с высоконагруженными проектами. Первую статью цикла “Архитектура высоконагруженных проектов: Facebook” я опубликовал пару дней назад. Дальше я продолжу публиковать материалы о технологиях, которые используются в фейсбуке: HipHop, Haystack, BigPipe, Cassandra, Scribe, Hadoop, Hive, Thrift, Varnish, GateKeeper и другие. Эта статья посвящена Cassandra.

Это перевод статьи, датированной 1м сентября 2009 года, следует это учесть при прочтении. — прим. пер.

В последний месяц или два команда инженеров Digg потратила совсем немного времени на изучение, тестирование и окончательное внедрение Cassandra в продакшен. Это был очень веcёлый проект, но до того, как веселье началось, нам пришлось потратить какое-то время на выяснение того, что же представляет собой модель данных Cassandra… фраза «WTF is a «super column»» («что за фигня этот суперстолбец?») была произнесена не один раз.

Если вы работали ранее с РСУБД (это касается почти всех), вы вероятно будете немного обескуражены некоторыми названиями при изучении модели данных Cassandra. Мне и моей команде в Digg потребовалось несколько дней обсуждений, прежде чем мы «врубились». Пару недель назад в списке рассылки разработчиков шёл процесс bikeshed-а на тему полностью новой схемы именования для разрешения неразберихи. На всём протяжении дискуссии я думал: «может, если будет несколько нормальных примеров, люди не будут так смущены названиями». Так, это моя попытка объяснения модели данных Cassandra; она предназначена для того, чтобы вы ознакомились, но не уходили в дебри, и, надеюсь, это поможет прояснить некоторые вещи.

Кусочки

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

Столбец

Столбец (Column) — это минимальный элемент данных. Это триплет, содержащий имя, значение и метку времени. Столбец, представленный в обозначениях JSON:

{
name: "emailAddress", // имя
value: "arin@example.com", // значение
timestamp: 123456789 // метка времени
}

Это всё. Для простоты можно опустить метку времени. И воспринимать столбец как пару имя/значение. Также, стоит отметить, что и имя, и значение бинарны (технически byte[]) и могут быть любой длины.

СуперСтолбец

Суперстолбец (SuperColumn) — это совокупность бинарного имени и значения, которое по сути является таблицей, содержащей неограниченное число столбцов, с ключом — именем столбца. Снова представим это в виде JSON:

{
name: "homeAddress",
// неограниченное число столбцов:
value: {
// ключ - это имя столбца
street: {name: "street", value: "1234 x street", timestamp: 123456789},
city: {name: "city", value: "san francisco", timestamp: 123456789},
zip: {name: "zip", value: "94107", timestamp: 123456789},
}
}
Столбец против СуперСтолбца

И столбцы, и суперстолбцы — это пары имени и значения. Ключевое различие в том, что значение обычного столбца — это «строка», а значение суперстолбца — это таблица столбцов. Это главное различие. Их значения содержат разные типы данных. Другое незначительное различие в том, что суперстолбец не содержит метку времени.

Перед тем, как начнём совмещать

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

{
name: "homeAddress",
value: {
street: {name: "street", value: "1234 x street", timestamp: 123456789},
city: {name: "city", value: "san francisco", timestamp: 123456789},
zip: {name: "zip", value: "94107", timestamp: 123456789},
}
}

к

homeAddress: {
street: "1234 x street",
city: "san francisco",
zip: "94107",
}

Группируем их

Есть структура, используемая для группировки и столбцов, и суперстолбцов…эта структура называется семейством столбцов (ColumnFamily) и существует соответственно в двух вариациях — обычная и супер.

Семейство столбцов

Семейство столбцов — это структура, содержащая неограниченное число строк. Ого, ты сказал строк? Да — строк 🙂 Чтобы это проще уложилось в голове, просто думайте о них, как о строках таблицы в РСУБД.

Итак, каждая строка имеет установленный клиентом (вами) ключ и содержит набор столбцов. Повторюсь, ключи в наборе — это имена столбцов и значения — это сами столбцы:

UserProfile = {
phatduckk: { // это ключ строки внутри семейства столбцов
// у нас есть неограниченное число столбцов в этой строке
username: "phatduckk",
email: "phatduckk@example.com",
phone: "(900) 976-6666"
}, // конец строки
ieure: { // это ключ другой строки внутри семейства столбцов
// у нас есть неограниченное число столбцов и в этой строке тоже
username: "ieure",
email: "ieure@example.com",
phone: "(888) 555-1212"
age: "66",
gender: "undecided"
},
}

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

Вы можете думать об этом как о хэштаблице/словаре или ассоциативном массиве. Если вы начали так думать, тогда вы на верном пути.

Хочу заострить ваше внимание на том, что на этом уровне нет никакой обязательной схемы. У строк нет предопределённого списка столбцов, которые они содержат. В нашем примере выше вы видите, что строка с ключом «ieure» содержит столбцы с именами «age» и «gender», тогда как строка, идентифицируемая ключом «phatduckk» не содержит. Это стопроцентная гибкость: одна строка может содержать 1989 столбцов, тогда как в другой будет всего лишь 2. Одна строка может содержать столбец с именем «foo», тогда как все остальные не будут. Вот она — перспектива отсутствия схемы в Cassandra.

Семейство столбцов тоже может быть супер

Итак, семейство столбцов может быть типа Standard или Super.

То, что мы рассмотрели выше — это был пример типа Standard. Стандартным он является потому, что все его строки содержат таблицу обычных (не супер) столбцов… там нет суперстолбцов.

Если же семейство столбцов имеет тип Super, то напротив: каждая строка содержит набор суперстолбцов. Набор, где ключами являются имена суперстолбцов, а значениями — сами суперстолбцы. И, просто для ясности: семейство суперстолбцов не содержит обычных столбцов. Вот пример:

AddressBook = {
phatduckk: { // это ключ строки внутри семейства суперстолбцов
// ключ - имя владельца адресной книги

// у нас есть неограниченное число суперстолбцов в этой строке
// ключи внутри строки – это имена суперстолбцов
// каждый из этих суперстолбцов – это запись в адресной книге
friend1: {street: “8th street”, zip: “90210”, city: “Beverley Hills”, state: “CA”},

// это запись для John’а в адресной книге phatduckk’а
John: {street: “Howard street”, zip: “94404”, city: “FC”, state: “CA”},
Kim: {street: “X street”, zip: “87876”, city: “Balls”, state: “VA”},
Tod: {street: “Jerry street”, zip: “54556”, city: “Cartoon”, state: “CO”},
Bob: {street: “Q Blvd”, zip: “24252”, city: “Nowhere”, state: “MN”},

// у нас может быть неограниченное число суперстолбцов в этой строке
}, // конец строки
ieure: { // это ключ другой строки внутри семейства суперстолбцов
joey: {street: “A ave”, zip: “55485”, city: “Hell”, state: “NV”},
William: {street: “Armpit Dr”, zip: “93301”, city: “Bakersfield”, state: “CA”},
},
}

Пространство ключей

Пространство ключей (Keyspace) — это то, что объединяет все ваши данные. Все ваши семейства столбцов находятся в пространстве ключей. Ваше пространство ключей вероятно будет соответствовать вашему приложению.

Итак, пространство ключей может содержать несколько семейств столбцов, но это не значит, что они как-то будут зависеть друг от друга. Например, их нельзя JOIN’ить, как таблицы в MySQL. Также, только потому, что ColumnFamily_1 содержит строку с ключом «phatduckk», это не значит, что ColumnFamily_2 тоже её содержит.

Сортировка

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

Столбцы внутри строк всегда отсортированы по имени столбца. Это важно, так что повторю: столбцы всегда сортируются по имени! Как именно сравниваются имена зависит от параметра CompareWith семейства столбцов. По-умолчанию у вас есть следующие варианты: BytesType, UTF8Type, LexicalUUIDType, TimeUUIDType, AsciiType, и LongType. Каждый из этих вариантов рассматривает имена столбцов как различные типы данных, обеспечивая некоторую гибкость. Например: использование LongType будет трактовать имена столбцов как 64-битные целые числа. Давайте попробуем и проясним это, взглянув на данные до и после сортировки:

// Это список всех столбцов из одной строки, в случайном порядке
// Cassandra "никогда" не хранит данные в случайном порядке. Это просто пример
// Также, можно игнорировать значения - они не играют вообще никакой роли в сортировке

{name: 123, value: "hello there"},
{name: 832416, value: "kjjkbcjkcbbd"},
{name: 3, value: "101010101010"},
{name: 976, value: "kjjkbcjkcbbd"}

Так, учитывая, что мы используем вариант LongType, эти столбцы будут выглядеть так после сортировки:

<!-- определение семейства столбцов в storage-conf.xml -->
<ColumnFamily CompareWith="LongType" Name="CF_NAME_HERE"/>
// Заметьте, столбцы рассматриваются как целые числа
// фактически, наши столбцы идут в числовом порядке
{name: 3, value: "101010101010"},
{name: 123, value: "hello there"},
{name: 976, value: "kjjkbcjkcbbd"},
{name: 832416, value: "kjjkbcjkcbbd"}

Как видите, имена столбцов сравнивались, как если бы они были 64-битными целыми. Если бы мы сейчас использовали другой вариант CompareWith, мы бы получили другой результат. Если бы мы установили CompareWith как UTF8Type, имена столбцов трактовались бы как строки в кодировке UTF8 и образовали такой порядок:

<!-- определение семейства столбцов в storage-conf.xml -->
<ColumnFamily CompareWith="UTF8Type" Name="CF_NAME_HERE"/>
// имена столбцов рассматриваются как строки в кодировке UTF8
{name: 123, value: "hello there"},
{name: 3, value: "101010101010"},
{name: 832416, value: "kjjkbcjkcbbd"},
{name: 976, value: "kjjkbcjkcbbd"}

Совершенно другой результат!

Этот принцип сортировки применим и к суперстолбцам, но у нас появляется ещё одно измерение, с которым надо работать: мы определяем не только то, как должны сортироваться суперстолбцы, но также и то, как должны сортиваться столбцы внутри суперстолбцов. Сортировка столбцов внутри суперстолбцов определяется значением параметра CompareSubcolumnsWith. Вот пример:

// Это строка с двумя суперстолбцами в ней
// в данный момент они в случайном порядке

{ // первый суперстолбец в строке
name: “workAddress”,
// и столбцы в нём
value: {
street: {name: “street”, value: “1234 x street”},
city: {name: “city”, value: “san francisco”},
zip: {name: “zip”, value: “94107”}
}
},
{ // другой суперстолбец в той же строке
name: “homeAddress”,
// и столбцы в нём
value: {
street: {name: “street”, value: “1234 x street”},
city: {name: “city”, value: “san francisco”},
zip: {name: “zip”, value: “94107”}
}
}

Теперь, если мы решим установить для CompareSubcolumnsWith и CompareWith значение UTF8Type, мы получим следующий результат:

// Теперь они отсортированы

{
name: “homeAddress”,
value: {
city: {name: “city”, value: “san francisco”},
street: {name: “street”, value: “1234 x street”},
zip: {name: “zip”, value: “94107”}
}
},
{
name: “workAddress”,
value: {
city: {name: “city”, value: “san francisco”},
street: {name: “street”, value: “1234 x street”},
zip: {name: “zip”, value: “94107”}
}
}

Хочу заметить, что в последнем примере CompareSubcolumnsWith и CompareWith оба установлены в UTF8Type, но это не обязательно. Вы можете сочетать значения параметров CompareSubcolumnsWith и CompareWith как вам угодно.

И последнее, о чём я хочу упомянуть в связи с сортировкой — это то, что вы можете написать собственный класс для выполнения сортировки. Сортирующий механизм подключается независимо… вы можете установить для CompareSubcolumnsWith и/или CompareWith любое подходящее имя класса, как только этот класс будет реализовывать интерфейс org.apache.cassandra.db.marshal.IType (то есть, вы можете создать свою схему сравнения для сортировки).

Пример схемы

Ладно, теперь у нас есть все кусочки паззла, так что давайте соберём их вместе и смоделируем простое приложение для блога. Будем моделировать приложение со следующими спецификациями:

  • поддержка одного блога
  • может быть несколько авторов
  • записи содержат заголовок, тело, уникальную метку и дату публикации
  • записи могут быть ассоциированы с любым чисом тегов
  • люди могут оставлять комментарии, но не могут регистрироваться: они вводят информацию о себе каждый раз заново (просто упрощаем)
  • комментарии содержат текст, время, когда были оставлены, и имя комментатора
  • должна быть возможность показать все посты в порядке, обратном хронологическому (самое новое вверху)
  • должна быть возможность показать все посты по тегу в порядке, обратном хронологическому

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

Семейство столбцов Authors

Моделирование семейства столбцов авторов — это достаточно базово; мы не будем здесь делать ничего крутого. Мы назначим каждому автору по строке и ключу и это будет полное имя автора. Каждый столбец в строке будет представлять собой определённый параметр профиля автора.

Это пример использования строк как объектов… в данном случае объектов Author. С таким подходом каждый столбец будет служить в качестве свойства объекта. Очень просто. Я хочу обратить внимание, что так как нет никакого определения того, как столбцы должны быть представлены в строке, мы можем задать это определение сами.

Мы будем получать строки из нашего семейства столбцов с помощью ключа и выбирать все столбцы для каждой строки (то есть, например, мы не будем выбирать только первые 3 столбца из строки с ключом «foo»). Это означает, что для нас не имеет значения, как будут отсортированы столбцы, так что будем использовать сортировку BytesType, потому что она не требует никакой валидации для имён столбцов.

<!--
ColumnFamily: Authors
Мы будем хранить тут данные об авторах.

Ключ строки => имя автора (подразумевается, что имена уникальны)
Имя столбца: параметр записи (email, bio и т.д.)
Значение столбца: соответствующее значение параметра

Выборка: получение автора по имени (выбираем все столбцы из соответствующей имени строки)

Authors : { // семейство столбцов
Arin Sarkissian : { // ключ строки
// столбцы, параметры профиля
numPosts: 11,
twitter: phatduckk,
email: arin@example.com,
bio: “bla bla bla”
},
// и другие авторы
Author 2 {

}
}
–>
<ColumnFamily CompareWith=”BytesType” Name=”Authors”/>

Семейство столбцов BlogEntries

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

Ключом каждой строки будет уникальная метка (slug). Так что, для выборки единственной записи мы будем искать её по этой метке.

<!--
ColumnFamily: BlogEntries
Все записи в блоге будут храниться тут.

Ключ строки => уникальная метка поста (она будет использоваться в урлах)
Имя столбца: параметр записи (заголовок, тело, и т.д.)
Значение столбца: соответствующее значение параметра

Выборка: получение записи по уникальной метке (всегда выбираем все столбцы в строке)

к вашему сведению: параметр tags денормализирован… это разделённый запятой список тегов.
я не использую JSON, чтобы не путать наши обозначения, но очевидно,
вы можете хранить данные как угодно, если ваше приложение знает, как с ними работать

BlogEntries : { // семейство столбцов
i-got-a-new-guitar : { // ключ строки – уникальная метка записи (slug)
title: This is a blog entry about my new, awesome guitar,
body: this is a cool entry. etc etc yada yada
author: Arin Sarkissian // ключ строки в семействе столбцов Authors
tags: life,guitar,music
pubDate: 1250558004 // дата публикации в формате unixtime
slug: i-got-a-new-guitar
},
// другие записи
another-cool-guitar : {

tags: guitar,
slug: another-cool-guitar
},
scream-is-the-best-movie-ever : {

tags: movie,horror,
slug: scream-is-the-best-movie-ever
}
}
–>
<ColumnFamily CompareWith=”BytesType” Name=”BlogEntries”/>

Семейство столбцов TaggedPosts

Итак, наконец будет что-то интересное. Это семейство столбцов покажет нам новый уровень. Оно будет отвечать за хранение связей между тегами и постами. Оно будет хранить не только связи, но и позволит нам выбирать все записи в блоге по определённму тегу, в отсортированном порядке (помните всё, что мы знаем о сортировке?).

Особенность решения, которую я хочу отметить, в том, что логика нашего приложения должна прикреплять к каждой записи BlogEntry тег “__notag__” (я его только что придумал). Такой тег позволит нам использовать это семейство стобцов также и для хранения списка всех записей в блоге в отсортированном виде. Это небольшой трюк, который даст возможность использовать только одно семейство столбцов для двух выборок: «показать все последние посты» и «показать все последние посты с тегом foo».

Согласно этой модели данных, записи с тремя тегами будут соответствовать по 1 столбцу в 4 строках. По одной для каждого тега и одна для служебного тега “__notag__”.

Так как мы решили, что будем отображать списки записей в хронологическом порядке, нам надо сделать так, чтобы имена столбцов были типа TimeUUID и установить для параметра CompareWith значение TimeUUIDType. Это отсортирует столбцы по времени. Так что использование запросов типа «получить последние 10 записей с тегом foo» будет очень эффективной операцией.

Теперь, когда мы захотим отобразить последние 10 записей (на главной, например), нам нужно будет:

  1. взять последние 10 столбцов по ключу “__notag__” (тег «все посты»)
  2. пройтись циклом по этому набору столбцов
  3. в цикле, мы знаем, что значение каждого столбца — это ключ строки в семействе столбцов BlogEntries
  4. так что мы используем этот ключ, чтобы получить строку для этой записи из семейства столбцов BlogEntries. так мы получаем все данные о записи
  5. один из столбцов в строке BlogEntries назван author и его значение — это ключ в семействе столбцов Authors, и мы используем его, чтобы получить данные профиля автора
  6. итак, у нас есть данные поста и данные автора
  7. дальше мы разбиваем столбец с тегами, чтобы получить список тегов
  8. теперь у нас есть всё, чтобы отобразить этот пост (пока без комментариев — это страница списка постов, а не конкретного поста)

Мы можем проделать эту процедуру используя любой тег… она работает и для «всех записей», и для «записей с тегом foo». Вроде неплохо.

<!--
ColumnFamily: TaggedPosts
Вспомогательный индекс для определения, какие записи в BlogEntries соответствуют тегу

Ключ строки => тег
Имена столбцов: TimeUUIDType
Значения столбцов: ключ строки в семействе столбцов BlogEntries

Выборка: получение среза записей с тегом “foo”

Мы используем это семейство столбцов чтобы определить, какие записи блога показывать по тегу
Мы будем чуточку гетто и используем строку __notag__, подразумевая “тег не имеет значения”.
Каждой записи будет соответствовать столбец в этой строке…
Это значит, что у нас будет “кол-во тегов + 1” столбцов на каждый пост.

TaggedPosts : { // семейство столбцов
// записи блога с тегом “guitar”
guitar : {
timeuuid_1 : i-got-a-new-guitar,
timeuuid_2 : another-cool-guitar,
},
// все записи в блоге
__notag__ : {
timeuuid_1b : i-got-a-new-guitar,

// заметьте, что такой столбец есть и в строке “guitar”
timeuuid_2b : another-cool-guitar,

// а такой – в строке “movie”
timeuuid_2b : scream-is-the-best-movie-ever,
},
// записи блога с тегом “movie”
movie: {
timeuuid_1c: scream-is-the-best-movie-ever
}
}
–>
<ColumnFamily CompareWith=”TimeUUIDType” Name=”TaggedPosts”/>

Семейство столбцов Comments

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

У нас будет 1 строка на пост. В качестве ключей будем использовать те же ключи, что использовались для постов. В строках у нас будут суперстолбцы, для каждого комментария свой. Именами суперстолбцов будут уникальные идентификаторы типа TimeUUIDType. Так мы гарантируем, что все комментарии к посту отсортированы в хронологическом порядке. Столбцы в каждом суперстолбце будут параметрами комментария (имя комментатора, время комментария, и т.д.)

Итак, это довольно просто до сих пор… ничего сверхъестесственного.

<!--
ColumnFamily: Comments
Здесь мы храним комментарии

Ключ строки => ключ строки в BlogEntries
Имя суперстолбца: TimeUUIDType

Выборка: получение всех комментариев к записи

Comments : {
// комментарии для scream-is-the-best-movie-ever
scream-is-the-best-movie-ever : {
// вначале старые комментарии
timeuuid_1 : { // имя суперстолбца
// все столбцы в суперстолбце – данные комментария
commenter: Joe Blow,
email: joeb@example.com,
comment: you’re a dumb douche, the godfather is the best movie ever
commentTime: 1250438004
},

… ещё комментарии к scream-is-the-best-movie-ever

// в конце – последние комментарии
timeuuid_2 : {
commenter: Some Dude,
email: sd@example.com,
comment: be nice Joe Blow this isnt youtube
commentTime: 1250557004
},
},

// комментарии для i-got-a-new-guitar
i-got-a-new-guitar : {
timeuuid_1 : {
commenter: Johnny Guitar,
email: guitardude@example.com,
comment: nice axe dawg…
commentTime: 1250438004
},
}

..
// ещё строки для других записей
}
–>
<ColumnFamily CompareWith=”TimeUUIDType” ColumnType=”Super”
CompareSubcolumnsWith=”BytesType” Name=”Comments”/>

Woot!

Это всё. Наше маленькое приложение блога смоделировано и готово к эксплуатации. Совсем немного переварить, и окончите вы с очень маленьким куском XML в вашем storage-conf.xml:

<Keyspace Name="BloggyAppy">
<!-- ... -->
<!-- CF definitions -->
<ColumnFamily CompareWith="BytesType" Name="Authors"/>
<ColumnFamily CompareWith="BytesType" Name="BlogEntries"/>
<ColumnFamily CompareWith="TimeUUIDType" Name="TaggedPosts"/>
<ColumnFamily CompareWith="TimeUUIDType" Name="Comments"
CompareSubcolumnsWith="BytesType" ColumnType="Super"/>
</Keyspace>

Теперь всё, что вам нужно сделать — разобраться, как записывать и считывать данные из Cassandra. Это можно осуществлять с помощью Thrift Interface. На wiki-странице API Cassandra сделана приличная работа по объяснению, как с этим работать, так что я не буду вдаваться во все эти детали. Но, вообще, вы можете просто скомпилировать файл cassandra.thrift и использовать сгенерированный код для доступа к API. Также вы можете воспользоваться преимуществами клиента Ruby или клиента для Python.

Ладно… надеюсь, всё это дало вам почувствовать, что же всё-таки за фигня этот суперстолбец и вы начнёте создавать классные приложения.

Также предлагаю вам посмотреть видео “Scaling Facebook with OpenSource Tools”.

Тезисы видео (если вам лень смотреть):

  • Authors:
  • o   Can load 25TB at network bandwidth over Cassandra Cluster

o   Prashant Malik
o   Karthnik Ranganathan
o   Avinash Lakshman

  • Structured storage system over P2p (keys are consistent hashed over servers)
  • Initially aimed at email inbox search problem
  • Design goals:

o   Cost Effective
o   Highly Available
o   Incrementally Scalable
o   Efficient data layout
o   Minimal administration

  • Why Cassandra

o   MySQL drives too many random I/Os
o   File-based solutions require far too many locks

  • What is Cassandra

o   Structured storage over a distributed cluster
o   Redundancy via replication
o   Supports append/insert without reads
o   Supports a caching layer
o   Supports Hadoop operations

  • Cassandra Architecture

o   Core Cassandra Services:
§  Messaging (async, non-blocking)
§  Failure detector
§  Cluster membership
§  Partitioning scheme
§  Replication strategy
o   Cassandra Middle Layer
§  Commit log
§  Mem-table
§  Compactions
§  Hinted handoff
§  Read repair
§  Bootstrap
o   Cassandra Top Layer
§  Key, block, & column indexes
§  Read consistency
§  Touch cache
§  Cassandra API
§  Admin API
§  Read Consistency
o   Above the top layer:
§  Tools
§  Hadoop integration
§  Search API and Routing

  • Cassandra Data Model

o   Key (uniquely specifies a “row”)
§  Any arbitrary string
o   Column families are declared or deleted in advance by administrative action
§  Columns can be added or deleted dynamically
§  Column families have attribute:

  • Name: arbitrary string
  • Type: simple,

o   Key can “contain” multiple column families
§  No requirement that two keys have any overlap in columns
o   Columns can be added or removed arbitrarily from column families
o   Columns:
§  Name: arbitrary string
§  Value: non-indexed blob
§  Timestamp (client provided)
o   Column families have sort orders
§  Time-based sort or name-based sort
o   Super-column families:
§  Big tables calls them locality groups
§  Super-column families have a sort order
§  Essentially a multi-column index
o   System column families
§  For internal use by Cassandra
o   Example from email application
§  Mail-list (sorted by name)

  • All mail that includes a given word

§  Thread-list (sorted by time)

  • All threads that include a given word

§  User-list (sorted by time)

  • All mail that includes a given word user
  • Cassandra API

o   Simple get/put model

  • Write model:

o   Quorum write or aysnc mode (used by email application)
o   Async: send request to any node
§  That node will push the data to appropriate nodes but return to client immediately
o   Quorum write:
§  Blocks until quorum is reached
o   If node down, then write to another node with a hint saying where it should be written two
§  Harvester every 15 min goes through and find hints and moves the data to the appropriate node
o   At write time, you first write to a commit log (sequential)
§  After write to log it is sent to the appropriate nodes
§  Each node receiving write first records it in a local log

  • Then makes update to appropriate memtables (1 for each column family)

§  Memtables are flushed to disk when:

  • Out of space
  • Too many keys (128 is default)
  • Time duration (client provided – no cluster clock)

§  When memtables written out two files go out:

  • Data File
  • Index File

o   Key, offset pairs (points into data file)

o   Bloom filter (all keys in data file)

§  When a commit log has had all its column families pushed to disk, it is deleted

  • Data files accumulate over time.  Periodically data files are merged sorted into a new file (and creates new index)
  • Write properties:

o   No locks in critical path
o   Sequential disk access only
o   Behaves like a write through cache
§  If you read from the same node, you see your own writes.  Doesn’t appear to provide any guarantee on read seeing latest change in failure case
o   Atomicity guaranteed for a key
o   Always writable

  • Read Path:

o   Connect to any node
o   That node will route to the closes data copy which services immediately
o   If high consistency required, don’t return from local immediately
§  First send digest request to all replicas
§  If delta is found, the updates are sent to the nodes that don’t have current data (read repair)

  • Replication supported via multiple consistent hash rings:

o   Servers are hashed over ring
o   Keys are hashed over ring
o   Redundancy via walking around the ring and placing on the next node (rack position unaware) or on the next node on a different rack (rack aware) or on a next system in a different data center (implication being that the ring can span data centers)

  • Cluster membership

o   Cluster membership and failure detection via gossip protocol

  • Accrual failure detector

o   Default sets PHI to 5 in Cassandra
o   Detection is 10 to 15 seconds with PHI=5

  • UDP control messages and TCP for data messages
  • Complies with Staged Event Driven Architecture (SEDA)
  • Email system:

o   100m users
o   4B threads

o   25TB with 3x replication
o   Uses and joins across 4 tables:
§  Mailbox (user_id to thread_id mapping)
§  Msg_threads (thread to subject mapping)
§  Msg_store (thread to message mapping)
§  Info (user_id to user name mapping)

  • Able to load using Hadoop at 1.5TB/hour

От переводчика: старался перевести максимально близко к оригиналу. Надеюсь на конструктивную критику.

Оригинал перевода статьи: WTF is a SuperColumn? Введение в модель данных Cassandra // Переводчик Сергей

Оригинал статьи: WTF is a SuperColumn? An Intro to the Cassandra Data Model // Автор Арин Саркесян

Leave a Comment