Русский
Русский
English
Статистика
Реклама

Database design

Создаем схему базы данных на SQLAlchemy

07.02.2021 18:17:06 | Автор: admin

Много уже говорилось о том, что SQLAlchemy - одна из самых популярных библиотек для создания схем баз данных. Сегодня рассмотрим несложный пример по созданию небольшой схемы данных для приложения по поиску цитат. В качестве СУБД будем использовать PostgreSQL.

Подход к определению моделей будем использовать декларативный, так как, на мой взгляд, он проще и понятнее классического подхода, основанного на mapper. Предварительно набросаем er-диаграмму.

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

Построим схему в SQLAlchemy в соответствии с диаграммой. Для удобства запросов к базе и манипуляций с моделями включим в таблицу Quote relationship.

from sqlalchemy import Column, ForeignKey, Integer, String, Text, Date, DateTimefrom sqlalchemy.orm import relationshipfrom sqlalchemy.ext.declarative import declarative_baseBase = declarative_base()class Topic(Base):    __tablename__ = 'topic'    __tableargs__ = {        'comment': 'Темы цитат'    }    topic_id = Column(        Integer,        nullable=False,        unique=True,        primary_key=True,        autoincrement=True    )    name = Column(String(128), comment='Наименование темы')    description = Column(Text, comment='Описание темы')    def __repr__(self):        return f'{self.topic_id}{self.name}{self.description}'class Author(Base):    __tablename__ = 'author'    __tableargs__ = {        'comment': 'Авторы цитат'    }    author_id = Column(        Integer,        nullable=False,        unique=True,        primary_key=True,        autoincrement=True    )    name = Column(String(128), comment='Имя автора')    birth_date = Column(Date, comment='Дата рождения автора')    country = Column(String(128), comment='Страна рождения автора')    def __repr__(self):        return f'{self.author_id}{self.name}{self.birth_date}{self.country}'class Quote(Base):    __tablename__ = 'quote'    __tableargs__ = {        'comment': 'Цитаты'    }    quote_id = Column(        Integer,        nullable=False,        unique=True,        primary_key=True,        autoincrement=True    )    text = Column(Text, comment='Текст цитаты')    created_at = Column(DateTime, comment='Дата и время создания цитаты')    author_id = Column(Integer,  ForeignKey('author.author_id'), comment='Автор цитаты')    topic_id = Column(Integer, ForeignKey('topic.topic_id'), comment='Тема цитаты')    author = relationship('Author', backref='quote_author', lazy='subquery')    topic = relationship('Topic', backref='quote_topic', lazy='subquery')    def __repr__(self):        return f'{self.text}{self.created_at}{self.author_id}{self.topic_id}'

Пройдемся по коду, некоторые вещи могут показаться элементарными, можете их пропустить. Стоит отметить, что при декларативном подходе все объекты таблиц наследуются от Base . Для каждой таблицы есть возможность добавить ее название в базе данных, а так же комментарий к ней с помощью встроенных __tablename__ и __tableargs__ .

Для каждого из столбцов таблицы есть возможность задать различные параметры, как и в различных СУБД. Внешние ключи задаются через название таблицы и поле, которое будет являться внешним ключом. Для удобства операций с данными используется relationship. Он позволяет связать объекты таблиц, а не только отдельные поля, как происходит при объявлении только внешних ключей. Параметр lazy определяет, как связанные объекты загружаются при запросе через отношения. Значения joined и subquery фактически делают одно и то же: объединяют таблицы и возвращают результат, но под капотом устроены по-разному, поэтому могут быть различия в производительности, поскольку они по-разному объединяются в таблицы.

Магический метод __repr__ фактически определяет, что будет выведено на экран при распечатке таблицы.

После создания схемы данных, развернуть таблицы можно разными способами. Для проверки отсутствия противоречий можно использовать следующие строки, предварительно создав базу данных (пример приведен для postgresql).

engine = create_engine('postgresql://user:password@host/tablename')Base.metadata.create_all(engine)

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

Подробнее..
Категории: Python , Sql , Python 3 , Sqlalchemy , Database design

SQLAlchemy а ведь раньше я презирал ORM

05.06.2021 22:17:23 | Автор: admin

Так вышло, что на заре моей карьеры в IT меня покусал Oracle -- тогда я ещё не знал ни одной ORM, но уже шпарил SQL и знал, насколько огромны возможности БД.

Знакомство с DjangoORM ввело меня в глубокую фрустрацию. Вместо возможностей -- хрена с два, а не составной первичный ключ или оконные функции. Специфические фичи БД проще забыть. Добивало то, что по цене нулевой гибкости мне продавали падение же производительности -- сборка ORM-запроса не бесплатная. Ну и вишенка на торте -- в дополнение к синтаксису SQL надо знать ещё и синтаксис ORM, который этот SQL сгенерирует. Недостатки, которые я купил за дополнительную когнитивную нагрузку -- вот уж где достижение индустрии. Поэтому я всерьёз считал, что без ORM проще, гибче и в разы производительнее -- ведь у вас в руках все возможности БД.

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

Опыт и как результат субъективная система взглядов

Я занимался оптимизацией SQL-запросов. Мне удавалось добиться стократного и более уменьшения cost запросов, в основном для Oracle и Firebird. Я проводил исследования, экспериментировал с индексами. Я видел в жизни много схем БД: среди них были как некоторое дерьмо, так и продуманные гибкие и расширяемые инженерные решения.

Этот опыт сформировал у меня систему взглядов касательно БД:

  • ORM не позволяет забыть о проектировании БД, если вы не хотите завтра похоронить проект

  • Переносимость -- миф, а не аргумент:

    • Если ваш проект работает с postgres через ORM, то вы на локальной машине разворачиваете в докере postgres, а не работаете с sqlite

    • Вы часто сталкивались с переходом на другую БД? Не пишите только "Однажды мы решили переехать..." -- это было однажды. Если же это происходит часто или заявленная фича, оправданная разными условиями эксплуатации у разных ваших клиентов -- милости прошу в обсуждения

    • У разных БД свои преимущества и болячки, всё это обусловлено разными структурами данных и разными инженерными решениями. И если при написании приложения мы используем верхний мозг, мы пытаемся избежать этих болячек. Тема глубокая, и рассмотрена в циклах лекций Базы данных для программиста и Транзакции от Владимира Кузнецова

  • Структура таблиц определяется вашими данными, а не ограничениями вашей ORM

Естественно, я ещё и код вне БД писал, и касательно этого кода у меня тоже сформировалась система взглядов:

  • Контроллер должен быть тонким, а лучший код -- это тот код, которого нет. Код ORM -- это часть контроллера. И если код контроллера спрятан в библиотеку, это не значит, что он стал тонким -- он всё равно исполняется

  • Контроллер, выполняющий за один сеанс много обращений к БД -- это очень тонкий лёд

  • Я избегаю повсеместного использования ActiveRecord -- это верный способ как работать с неконсистентными данными, так и незаметно для себя сгенерировать бесконтрольное множество обращений к БД

  • Оптимизация работы с БД сводится к тому, что мы не читаем лишние данные. Есть смысл запросить только интересующий нас список колонок

  • Часть данных фронт всё равно запрашивает при инициализации. Чаще всего это категории. В таких случаях нам достаточно отдать только id

  • Отладка всех новых запросов ORM обязательна. Всегда надо проверять, что там ORM высрала (тут пара сочных примеров), дабы не было круглых глаз. Даже при написании этой статьи у меня был косяк как раз по этому пункту

Идея сокращения по возможности количества выполняемого кода в контроллере приводит меня к тому, что проще всего возиться не с сущностями, а сразу запросить из БД в нужном виде данные, а выхлоп можно сразу отдать сериализатору JSON.

Все вопросы данной статьи происходят из моего опыта и системы взглядов

Они могут и не найти в вас отголоска, и это нормально

Мы разные, и у нас всех разный фокус внимания. Я общался с разными разработчиками. Я видел разные позиции, от "да не всё ли равно, что там происходит? Работает же" до "я художник, у меня справка есть". При этом у некоторых из них были другие сильные стороны. Различие позиций -- это нормально. Невозможно фокусироваться на всех аспектах одновременно.

Мне, например, с большего без разницы, как по итогу фронт визуализирует данные, хотя я как бы фулстэк. Чем я отличаюсь от "да не всё ли равно, что там происходит"? Протокол? Да! Стратегия и оптимизация рендеринга? Да! Упороться в WebGL? Да! А что по итогу на экране -- пофиг.

Знакомство в SQLAlchemy

Первое, что бросилось в глаза -- возможность писать DML-запросы в стиле SQL, но в синтаксисе python:

order_id = bindparam('order_id', required=True)return \    select(        func.count(Product.id).label("product_count"),        func.sum(Product.price).label("order_price"),        Customer.name,    )\    .select_from(Order)\    .join(        Product,        onclause=(Product.id == Order.product_id),    )\    .join(        Customer,        onclause=(Customer.id == Order.customer_id),    )\    .where(        Order.id == order_id,    )\    .group_by(        Order.id,    )\    .order_by(        Product.id.desc(),    )

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

Естественно, я сразу стал искать, как тут дела с составными первичными ключами -- и они есть! И оконные функции, и CTE, и явный JOIN, и много чего ещё! Для особо тяжёлых случаев можно даже впердолить SQL хинты! Дальнейшее погружение продолжает радовать: я не сталкивался ни с одним вопросом, который решить было невозможно из-за архитектурных ограничений. Правда, некоторые свои вопросы я решал через monkey-patching.

Производительность

Насколько крутым и гибким бы ни было API, краеугольным камнем является вопрос производительности. Сегодня вам может и хватит 10 rps, а завтра вы пытаетесь масштабироваться, и если затык в БД -- поздравляю, вы мертвы.

Производительность query builder в SQLAlchemy оставляет желать лучшего. Благо, это уровень приложения, и тут масштабирование вас спасёт. Но можно ли это как-то обойти? Можно ли как-то нивелировать низкую производительность query builder? Нет, серьёзно, какой смысл тратить мощности ради увеличения энтропии Вселенной?

В принципе, нам на python не привыкать искать обходные пути: например, python непригоден для реализации числодробилок, поэтому вычисления принято выкидывать в сишные либы.

Для SQLAlchemy тоже есть обходные пути, и их сразу два, и оба сводятся к кэшированию по разным стратегиям. Первый -- применение bindparam и lru_cache. Второй предлагает документация -- future_select. Рассмотрим их преимущества и недостатки.

bindparam + lru_cache

Это самое простое и при этом самое производительное решение. Мы покупаем производительность по цене памяти -- просто кэшируем собранный объект запроса, который в себе кэширует отрендеренный запрос. Это выгодно до тех пор, пока нам не грозит комбинаторный взрыв, то есть пока число вариаций запроса находится в разумных пределах. В своём проекте в большинстве представлений я использую именно этот подход. Для удобства я применяю декоратор cached_classmethod, реализующий композицию декораторов classmethod и lru_cache:

from functools import lru_cachedef cached_classmethod(target):    cache = lru_cache(maxsize=None)    cached = cache(target)    cached = classmethod(cached)    return cached

Для статических представлений тут всё понятно -- функция, создающая ORM-запрос не должна принимать параметров. Для динамических представлений можно добавить аргументы функции. Так как lru_cache под капотом использует dict, аргументы должны быть хешируемыми. Я остановился на варианте, когда функция-обработчик запроса генерирует "сводку" запроса и параметры, передаваемые в сгенерированный запрос во время непосредственно исполнения. "Сводка" запроса реализует что-то типа плана ORM-запроса, на основании которой генерируется сам объект запроса -- это хешируемый инстанс frozenset, который в моём примере называется query_params:

class BaseViewMixin:    def build_query_plan(self):        self.query_kwargs = {}        self.query_params = frozenset()    async def main(self):        self.build_query_plan()        query = self.query(self.query_params)        async with BaseModel.session() as session:            respone = await session.execute(                query,                self.query_kwargs,            )            mappings = respone.mappings()        return self.serialize(mappings)
Некоторое пояснение по query_params и query_kwargs

В простейшем случае query_params можно получить, просто преобразовав ключи query_kwargs во frozenset. Обращаю ваше внимание, что это не всегда справедливо: флаги в query_params запросто могут поменять сам SQL-запрос при неизменных query_kwargs.

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

Сколько же памяти я заплатил за это? А немного. На все вариации запросов я расходую не более мегабайта.

future_select

В отличие от дубового первого варианта, future_select кэширует куски SQL-запросов, из которых итоговый запрос собирается очень быстро. Всем хорош вариант: и высокая производительность, и низкое потребление памяти. Читать такой код сложно, сопровождать дико:

stmt = lambdas.lambda_stmt(lambda: future_select(Customer))stmt += lambda s: s.where(Customer.id == id_)

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

Наброски фасада, решающего проблему дикого синтаксиса

По идее, future_select через FutureSelectWrapper можно пользоваться почти как старым select, что нивелирует дикий синтаксис:

class FutureSelectWrapper:    def __init__(self, clause):        self.stmt = lambdas.lambda_stmt(            lambda: future_select(clause)        )        def __getattribute__(self, name):        def outer(clause):            def inner(s):                callback = getattr(s, name)                return callback(clause)                        self.stmt += inner            return self        return outer

Я обращаю ваше внимание, что это лишь наброски. Я их ни разу не запускал. Необходимы дополнительные исследования.

Промежуточный вывод: низкую производительность query builder в SQLAlchemy можно нивелировать кэшем запросов. Дикий синтаксис future_select можно спрятать за фасадом.

А ещё я не уделил должного внимания prepared statements. Эти исследования я проведу чуть позже.

Как я открывал для себя ORM заново

Мы добрались главного -- ради этого раздела я писал статью. В этом разделе я поделюсь своими откровениями, посетившими меня в процессе работы.

Модульность

Когда я реализовывал на SQL дикую аналитику, старой болью отозвалось отсутствие модульности и интроспекции. При последующем переносе на ORM у меня уже была возможность выкинуть весь подзапрос поля FROM в отдельную функцию (по факту метод класса), а в последующем эти функции было легко комбинировать и на основании флагов реализовывать паттерн Стратегия, а также исключать дублирование одинакового функционала через наследование.

Собственные типы

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

Создание собственных простых типов рассмотрено в документации:

class ColorType(TypeDecorator):    impl = Integer    cache_ok = True    def process_result_value(self, value, dialect):        if value is None:            return        return color(value)    def process_bind_param(self, value, dialect):        if value is None:            return        value = color(value)        return value.value

Сыр-бор тут только в том, что мне стрельнуло хранить цвета не строками, а интами. Это исключает некорректность данных, но усложняет их сериализацию и десериализацию.

Теперь про ENUM. Меня категорически не устроило, что документация предлагает хранить ENUM в базе в виде VARCHAR. Особенно уникальные целочисленные Enum хотелось хранить интами. Очевидно, объявлять этот тип мы должны, передавая аргументом целевой Enum. Ну раз String при объявлении требует указать длину -- задача, очевидно, уже решена. Штудирование исходников вывело меня на TypeEngine -- и тут вместо примеров использования вас встречает "our source code is open 24/7". Но тут всё просто:

class IntEnumField(TypeEngine):    def __init__(self, target_enum):        self.target_enum = target_enum        self.value2member_map = target_enum._value2member_map_        self.member_map = target_enum._member_map_    def get_dbapi_type(self, dbapi):        return dbapi.NUMBER    def result_processor(self, dialect, coltype):        def process(value):            if value is None:                return            member = self.value2member_map[value]            return member.name        return process    def bind_processor(self, dialect):        def process(value):            if value is None:                return            member = self.member_map[value]            return member.value        return process

Обратите внимание: обе функции -- result_processor и bind_processor -- должны вернуть функцию.

Собственные функции, тайп-хинты и вывод типов

Дальше больше. Я столкнулся со странностями реализации json_arrayagg в mariadb: в случае пустого множества вместо NULL возвращается строка "[NULL]" -- что ни под каким соусом не айс. Как временное решение я накостылил связку из group_concat, coalesce и concat. В принципе неплохо, но:

  1. При вычитывании результата хочется нативного преобразования строки в JSON.

  2. Если делать что-то универсальное, то оказывается, что строки надо экранировать. Благо, есть встроенная функция json_quote. Про которую SQLAlchemy не знает.

  3. А ещё хочется найти workaround-функции в объекте sqlalchemy.func

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

Мне заказчик разрешил опубликовать код целого модуля!
from sqlalchemy.sql.functions import GenericFunction, register_functionfrom sqlalchemy.sql import sqltypesfrom sqlalchemy import func, literal_columndef register(target):    name = target.__name__    register_function(name, target)    return target# === Database functions ===class json_quote(GenericFunction):    type = sqltypes.String    inherit_cache = Trueclass json_object(GenericFunction):    type = sqltypes.JSON    inherit_cache = True# === Macro ===empty_string = literal_column("''", type_=sqltypes.String)json_array_open = literal_column("'['", type_=sqltypes.String)json_array_close = literal_column("']'", type_=sqltypes.String)@registerdef json_arrayagg_workaround(clause):    clause_type = clause.type    if isinstance(clause_type, sqltypes.String):        clause = func.json_quote(clause)    clause = func.group_concat(clause)    clause = func.coalesce(clause, empty_string)    return func.concat(        json_array_open,        clause,        json_array_close,        type_=sqltypes.JSON,    )def __json_pairs_iter(clauses):    for clause in clauses:        clause_name = clause.name        clause_name = "'%s'" % clause_name        yield literal_column(clause_name, type_=sqltypes.String)        yield clause@registerdef json_object_wrapper(*clauses):    json_pairs = __json_pairs_iter(clauses)    return func.json_object(*json_pairs)

В рамках эксперимента я также написал функцию json_object_wrapper, которая из переданных полей собирает json, где ключи -- это имена полей. Буду использовать или нет -- ХЗ. Причём тот факт, что эти макроподстановки не просто работают, а даже правильно, меня немного пугает.

Примеры того, что генерирует ORM
SELECT concat(  '[',  coalesce(group_concat(product.tag_id), ''),  ']') AS product_tags
SELECT json_object(  'name', product.name,  'price', product.price) AS product,

PS: Да, в случае json_object_wrapper я изначально допустил ошибку. Я человек простой: вижу константу -- вношу её в код. Что привело к ненужным bindparam на месте ключей этого json_object. Мораль -- держите ORM в ежовых рукавицах. Упустите что-то -- и она вам такого нагенерит! Только literal_column позволяет надёжно захардкодить константу в тело SQL-запроса.

Такие макроподстановки позволяют сгенерировать огромную кучу SQL кода, который будет выполнять логику формирования представлений. И что меня восхищает -- эта куча кода работает эффективно. Ещё интересный момент -- эти макроподстановки позволят прозрачно реализовать паттерн Стратегия -- я надеюсь, поведение json_arrayagg пофиксят в следующих релизах MariaDB, и тогда я смогу своё костылище заменить на связку json_arrayagg+coalesce незаметно для клиентского кода.

Выводы

SQLAlchemy позволяет использовать преимущества наследования и полиморфизма (и даже немного иннкапсуляции. Флеш-рояль, однако) в SQL. При этом она не загоняет вас в рамки задач уровня Hello, World! архитектурными ограничениями, а наоборот даёт вам максимум возможностей.

Субъективно это прорыв. Я обожаю реляционные базочки, и наконец-то я получаю удовольствие от реализации хитрозакрученной аналитики. У меня в руках все преимущества ООП и все возможности SQL.

Подробнее..

Сборник диаграмм классификаций баз данных

22.04.2021 00:12:56 | Автор: admin

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

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

Замечания и предложения приветствуются.

Классификации баз данных

Некоторые технологии и решения используемые в базах данных

Подробнее..

Перевод - recovery mode Инструменты обработки OLAP-запросов для Big Data

14.09.2020 22:10:58 | Автор: admin


Введение


Эта статья является компиляцией другой статьи. В ней я намерен сконцентрироваться на инструментах для работы с Big data, ориентированных на анализ данных.

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

Существует множество инструментов, используемых для работы с данными, каждый из которых имеет свои преимущества и недостатки. Большинство из них ориентировано на OLAP, но некоторые также оптимизированы для OLTP. Часть из них использует стандартные форматы и сосредоточена только на выполнении запросов, другие используют свой собственный формат или хранилище для передачи обработанных данных в источник в целях повышения производительности. Некоторые из них оптимизированы для хранения данных с использованием определенных схем, например звезда или снежинка, но есть и более гибкие. Подводя итог, имеем следующие противопоставления:

  • Хранилище данных против Озера
  • Hadoop против Автономного хранилища
  • OLAP против OLTP
  • Движок запросов против OLAP механизмов


Мы также рассмотрим инструменты для обработки данных с возможностью выполнения запросов.


Инструменты для обработки данных


Большинство упомянутых инструментов могут подключаться к серверу метаданных, например Hive, и выполнять запросы, создавать представления и т.п. Это часто используется для создания дополнительных (улучшенных) уровней отчетности.

Spark SQL предоставляет способ беспрепятственно смешивать запросы SQL с программами Spark, поэтому вы можете смешивать API DataFrame с SQL. Он имеет интеграцию с Hive и стандартное подключение через JDBC или ODBC, так что вы можете подключить Tableau, Looker или любой инструмент бизнес-аналитики к своим данным через Spark.



Apache Flink также предоставляет SQL API. Поддержка SQL в Flink основана на Apache Calcite, который реализует стандарт SQL. Он также интегрируется с Hive через HiveCatalog. Например, пользователи могут хранить свои таблицы Kafka или ElasticSearch в Hive Metastore с помощью HiveCatalog и повторно использовать их позже в запросах SQL.

Kafka тоже предоставляет возможности SQL. В целом большинство инструментов обработки данных предоставляют SQL интерфейсы.

Инструменты выполнения запросов


Этот тип инструментов ориентирован на унифицированный запрос к различным источникам данных в разных форматах. Идея состоит в том, чтобы направлять запросы к вашему озеру данных с помощью SQL, как если бы это была обычная реляционная база данных, хотя у нее есть некоторые ограничения. Некоторые из этих инструментов могут также выполнять запросы к NoSQL базам и многое другое. Эти инструменты предоставляют интерфейс JDBC для внешних инструментов, таких как Tableau или Looker, для безопасного подключения к вашему озеру данных. Инструменты запросов самый медленный вариант, но обеспечивают максимальную гибкость.

Apache Pig: один из первых инструментов наряду с Hive. Имеет собственный язык, отличный от SQL. Отличительным свойством программ созданных Pig является то, что их структура поддается существенному распараллеливанию, что, в свою очередь, позволяет им обрабатывать очень большие наборы данных. Это работает не в пользу более новых движков на базе SQL.

Presto: платформа от Facebook с открытым исходным кодом. Это распределенный механизм SQL-запросов для выполнения интерактивных аналитических запросов к источникам данных любого размера. Presto позволяет запрашивать данные там, где они находятся, включая Hive, Cassandra, реляционные базы данных и файловые системы. Она может выполнять запросы к большим наборам данных за секунды. Presto не зависит от Hadoop, но интегрируется с большинством его инструментов, особенно с Hive, для выполнения SQL-запросов.

Apache Drill: предоставляет механизм SQL запросов без схем для Hadoop, NoSQL и даже облачного хранилища. Он не зависит от Hadoop, но имеет множество интеграций с инструментами экосистемы, такими как Hive. Один запрос может объединять данные из нескольких хранилищ, выполняя оптимизацию, специфичную для каждого из них. Это очень хорошо, т.к. позволяет аналитикам обрабатывать любые данные как таблицу, даже если фактически они читают файл. Drill полностью поддерживает стандартный SQL. Бизнес-пользователи, аналитики и специалисты по обработке данных могут использовать стандартные инструменты бизнес-аналитики, такие как Tableau, Qlik и Excel, для взаимодействия с нереляционными хранилищами данных, используя драйверы Drill JDBC и ODBC. Кроме того, разработчики могут использовать простой REST API Drill в своих пользовательских приложениях для создания красивых визуализаций.

OLTP базы данных


Хотя Hadoop оптимизирован для OLAP, все же бывают ситуации, когда вы хотите выполнять запросы OLTP для интерактивного приложения.

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

Apache Phoenix построен на основе HBase и обеспечивает способ выполнения запросов OTLP в экосистеме Hadoop. Apache Phoenix полностью интегрирован с другими продуктами Hadoop, такими как Spark, Hive, Pig, Flume и Map Reduce. Он также может хранить метаданные, поддерживает создание таблиц и добавочные изменения с управлением версиями при помощи команд DDL. Это работает довольно быстро, быстрее, чем при использовании Drill или другого
механизма запросов.

Вы можете использовать любую крупномасштабную базу данных за пределами экосистемы Hadoop, такую как Cassandra, YugaByteDB, ScyllaDB для OTLP.

Наконец, очень часто бывает, что в быстрых базах данных любого типа, таких как MongoDB или MySQL, есть более медленное подмножество данных, обычно самые свежие. Упомянутые выше механизмы запросов могут объединять данные между медленным и быстрым хранилищами за один запрос.

Распределенное индексирование


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

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

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

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

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

OLAP базы данных


Тут мы рассмотрим базы данных, которые также могут предоставлять хранилище метаданных для схем запросов. Если сравнивать эти инструменты с системами выполнения запросов, эти инструменты также предоставляют хранилище данных и могут применяться для конкретных схем хранения данных (звездообразная схема). Эти инструменты используют синтаксис SQL. Spark, либо другие платформы могут с ними взаимодействовать.

Apache Hive: мы уже обсуждали Hive, как центральный репозиторий схем для Spark и других инструментов, чтобы они могли использовать SQL, но Hive также может хранить данные, так что вы можете использовать его в качестве хранилища. Он может получить доступ к HDFS или HBase. При запросе Hive он использует Apache Tez, Apache Spark или MapReduce, будучи намного быстрее Tez или Spark. Он также имеет процедурный язык под названием HPL-SQL. Hive это чрезвычайно популярное хранилище мета-данных для Spark SQL.

Apache Impala: это собственная аналитическая база данных для Hadoop, которую вы можете использовать для хранения данных и эффективных запросов к ним. Она может подключаться к Hive для получения метаданных с помощью Hcatalog. Impala обеспечивает низкую задержку и высокий уровень параллелизма для запросов бизнес-аналитики и аналитики в Hadoop (что не предоставляется пакетными платформами, такими как Apache Hive). Impala также масштабируется линейно, даже в многопользовательских средах, что является лучшей альтернативой для запросов, чем Hive. Impala интегрирована с собственной системой безопасности Hadoop и Kerberos для аутентификации, поэтому вы можете безопасно управлять доступом к данным. Она использует HBase и HDFS для хранения данных.



Apache Tajo: это еще одно хранилище данных для Hadoop. Tajo разработан для выполнения специальных запросов с малыми задержкой и масштабируемостью, онлайн-агрегирования и ETL для больших наборов данных, хранящихся в HDFS и других источниках данных. Он поддерживает интеграцию с Hive Metastore для доступа к общим схемам. Он также имеет множество оптимизаций запросов, он масштабируемый, отказоустойчивый и предоставляет интерфейс JDBC.

Apache Kylin: это новое распределенное хранилище аналитических данных. Kylin чрезвычайно быстр, поэтому его можно использовать для дополнения некоторых других баз данных, таких как Hive, для случаев использования, где важна производительность, таких как панели мониторинга или интерактивные отчеты. Это, вероятно, лучшее хранилище данных OLAP, но его сложно использовать. Другая проблема заключается в том, что из-за большой растягиваемости требуется больше места для хранения. Идея состоит в том, что если механизмы запросов или Hive недостаточно быстры, вы можете создать Куб в Kylin, который представляет собой многомерную таблицу, оптимизированную для OLAP, с предварительно вычисленными
значениями, которые вы можете запрашивать из панелей мониторинга или интерактивных отчетов. Он может создавать кубики прямо из Spark и даже почти в реальном времени из Kafka.



Инструменты OLAP


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

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

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

Apache Druid: это самый известный движок OLAP в реальном времени. Он ориентирован на данные временных рядов, но может использоваться для любых данных. Он использует свой собственный столбчатый формат, который может сильно сжимать данные, и он имеет множество встроенных оптимизаций, таких как инвертированные индексы, кодирование текста, автоматическое сворачивание данных и многое другое. Данные загружаются в реальном времени с использованием Tranquility или Kafka, которые имеют очень низкую задержку, хранятся в памяти в формате строки, оптимизированном для записи, но как только они поступают, они становятся доступными для запроса, как и предыдущие загруженные данные. Фоновая процесс отвечает за асинхронное перемещение данных в систему глубокого хранения, такую как HDFS. Когда данные перемещаются в глубокое хранилище, они разделяются на более мелкие фрагменты, сегрегированные по времени, называемые сегментами, которые хорошо оптимизированы для запросов с низкой задержкой. У такого сегмента есть отметка времени для нескольких измерений, которые вы можете использовать для фильтрации и агрегирования, и метрики, которые представляют собой предварительно вычисленные состояния. При пакетном приеме данные сохраняются непосредственно в сегменты. Apache Druid поддерживает проглатывание по принципу push и pull, имеет интеграцию с Hive, Spark и даже NiFi. Он может использовать хранилище метаданных Hive и поддерживает запросы Hive SQL, которые затем преобразуются в запросы JSON, используемые Druid. Интеграция Hive поддерживает JDBC, поэтому вы можете подключить любой инструмент бизнес-аналитики. Он также имеет собственное хранилище метаданных, обычно для этого используется MySQL. Он может принимать огромные объемы данных и очень хорошо масштабируется. Основная проблема в том, что в нем много компонентов, и им сложно управлять и развертывать.



Apache Pinot: это более новая альтернатива Druid с открытым исходным кодом от LinkedIn. По сравнению с Druid, он предлагает меньшую задержку благодаря индексу Startree, который выполняет частичное предварительное вычисление, поэтому его можно использовать для приложений, ориентированных на пользователя (он использовался для получения лент LinkedIn). Он использует отсортированный индекс вместо инвертированного, который работает быстрее. Он имеет расширяемую архитектуру плагинов, а также имеет множество интеграций, но не поддерживает Hive. Он также объединяет пакетную обработку и режим реального времени, обеспечивает быструю загрузку, интеллектуальный индекс и сохраняет данные в сегментах. Его легче и быстрее развернуть по сравнению с Druid, но на данный момент он выглядит немного недозрелым.

ClickHouse: будучи написанным на C++, этот движок обеспечивает невероятную производительность для запросов OLAP, особенно для агрегатов. Это похоже на реляционную базу данных, поэтому вы можете легко моделировать данные. Он очень прост в настройке и имеет множество интеграций.

Прочитайте эту статью, которая сравнивает 3 движка детально.

Начните с малого, изучив свои данные, прежде чем принимать решение. Эти новые механизмы очень мощные, но их сложно использовать. Если вы можете ждать по несколько часов, используйте пакетную обработку и базу данных, такую как Hive или Tajo; затем используйте Kylin, чтобы ускорить запросы OLAP и сделать их более интерактивными. Если этого недостаточно и вам нужно еще меньше задержки и данные в реальном времени, подумайте о механизмах OLAP. Druid больше подходит для анализа в реальном времени. Кайлин больше ориентирован на дела OLAP. Druid имеет хорошую интеграцию с Kafka в качестве потоковой передачи в реальном времени. Kylin получает данные из Hive или Kafka партиями, хотя планируется прием в реальном времени.

Наконец, Greenplum еще один механизм OLAP, больше ориентированный на искусственный интеллект.

Визуализация данных


Для визуализации есть несколько коммерческих инструментов, таких как Qlik, Looker или Tableau.

Если вы предпочитаете Open Source, посмотрите в сторону SuperSet. Это замечательный инструмент, который поддерживает все упомянутые нами инструменты, имеет отличный редактор и действительно быстрый, он использует SQLAlchemy, что обеспечивает поддержку многих баз данных.

Другие интересные инструменты: Metabase или Falcon.

Заключение


Есть широкий спектр инструментов, которые можно использовать для обработки данных, от гибких механизмов запросов, таких как Presto, до высокопроизводительных хранилищ, таких как Kylin. Единого решения не существует, я советую изучить имеющиеся данные и начать с малого. Механизмы запросов хорошая отправная точка из-за их гибкости. Затем для различных случаев использования вам может потребоваться добавить дополнительные инструменты чтобы достигнуть необходимого вам уровня обслуживания.

Обратите особое внимание на новые инструменты, такие как Druid или Pinot, которые обеспечивают простой способ анализа огромных объемов данных с очень низкой задержкой, сокращая разрыв между OLTP и OLAP с точки зрения производительности. У вас может возникнуть соблазн подумать об обработке, предварительном вычислении агрегатов и т.п., но подумайте об этих инструментах, если вы хотите упростить свою работу.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru