суббота, 10 декабря 2016 г.

System Design

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

Как делать деплой:
How Twitter deploys its widgets JavaScript
Manhattan software deployments: how we deploy Twitter’s large scale distributed database
Создание системы непрерывного развертывания: опыт Instagram

Как делать хранилище данных (правильнее сказать persistent layer):
The Google File System
Bigtable: A Distributed Storage System for Structured Data

Управление ресурсами кластера:

Балансировка нагрузки:

Обработка данных:

Best practices:

Консенсус:

Опыт компаний в построении систем:
A 360 Degree View Of The Entire Netflix Stack
A Brief History of Scaling LinkedIn
Redundant Array of Inexpensive Datacenters

Также есть тематические блоги, на которых публикуется много интересной и полезной информации. Ниже список тех, на которые я подписан:
LinedIn Engineering
Metadata
AMP Lab blog
Aphyr
All Things Distributed
Badoo Tech
Clouder Engineering Blog
Dropbox Engineering Blog
Twitter Engineering Blog
Facebook Code
Google Research Blog
Highscalability
Indeed Engineering
NGINX Blog
Netflix Thech Blog

среда, 18 февраля 2015 г.

Tail latency

Tail latency - это когда, например, по 90-му процентилю сервер обрабатывает наши запросы за 50 мллисекунд, а по 99-му за 1 секунду. Это очень плохо, т.к. некоторые пользователи будут ждать ответа непростительно долго.
В этой статье рассматриваются некоторые возможные источники tail latency
В таблице ниже сводка из статьи:

понедельник, 2 февраля 2015 г.

Специальные функции-члены в C++ 2

Здесь я рассказывал про специальные функции члены, при каких условиях они генерируются, при каких нет. При этом я не указал важный нюанс, касающийся шаблонов.
Допустим у нас есть код:
struct MyStruct {
    MyStruct() = default;

    template <typename T>
    MyStruct &operator=(const T&) {
        std::cout << "template copy assignment\n";
        return *this;
    }

    template <typename T>
    MyStruct(const T&) {
        std::cout << "template copy ctor\n";
    }
};

int main() {
    MyStruct c1;
    MyStruct c2 = c1;
    c1 = c2;
}
На экран, как не странно, не будет выведено ничего. В предыдущем посте я писал о том, что если определить оператор копирующего присваивания или конструктор копирования, то компилятор не генерирует оператор копирующего/перемещающего присваивания и конструктор копирования/перемещения. Но подобное правило не работает для шаблонных функций, поэтому компилятор сгенерировал все версии специальных функций по-умолчанию.

четверг, 20 ноября 2014 г.

Как устроен weak_ptr

Для чего нужен.

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

Как решить проблему циклических ссылок.

В объект счётчика ссылок добавляется счётчик слабых ссылок. weak_ptr увеличивает этот счётчик при создании и копировании. При этом объект счётчика ссылок не удаляется до тех пор, пока есть хотя бы один shared_ptr или один weak_ptr, т.к. weak_ptr необходимо обращаться к счётчику ссылок даже если нет ни одного живого shared_ptr. Но сам объект, на который указывает shared_ptr, может быть удалён, если больше не осталось живых shared_ptr.
Код объекта счётчика ссылок теперь будет выглядеть так:
template <typename T>
class sp_counted {
public:
    explicit sp_counted(T *p) noexcept
        : shared_count(1),
          weak_count(1),
          ptr(p) {}

    void add_ref() noexcept {
        ++shared_count;
    }

    void release() noexcept {
        if (!--shared_count) {
            // Если последний shared_ptr удалился, удаляем объект
            delete ptr;
            if (!--weak_count) {
                // Если нет слабых ссылок, удаляем объект счётчика
                destroy();
            }
        }
    }

    void add_ref_weak() noexcept {
        ++weak_count;
    }

    void release_weak() noexcept {
        if (!--weak_count) {
            // Если последний weak_ptr удалился, удаляем объект счётчика.
            // Т.к. shared_ptr тоже увеличивает weak_count при создании,
            // нет необходимости проверять значение shared_count
            destroy();
        }
    }

    size_t use_count() const noexcept {
        return shared_count.load();
    }

    // Попытка увеличить счётчик shared_count из weak_ptr
    // Потокобезопасен, lock-free
    void add_ref_lock() {
        // Сохраняем текущее значение shared_count
        size_t cur_value(shared_count.load());
        do {
            // Если счётчик сильных ссылок равен нулю (т.е. нет больше живых shared_ptr),
            // то новый shared_ptr создавать не из чего.
            if (!cur_value) {
                throw std::bad_weak_ptr();
            }
          // Пытаемся увеличить счётчик shared_count на единицу
          // Если в промежутке между сохранением shared_count в cur_value, shared_count изменился,
          // то операция compare_exchange_weak вернёт false, запишет новое значение shared_count в cur_value,
          // и цикл повторится
        } while (shared_count.compare_exchange_weak(cur_value, cur_value + 1));
    }

private:
    void destroy() noexcept {
        delete this;
    }

private:
    // Счётчик ссылок shared_ptr
    std::atomic<size_t> shared_count;
    // Счётчик ссылок weak_ptr
    std::atomic<size_t> weak_count;
    T *ptr;
};
Вопрос: почему вызов add_ref_lock безопасен? Ведь в другом потоке shared_ptr в деструкторе может удалить объект sp_counted, и обращение к переменной shared_count будет некорректным.
Ответ: потому что деструктор shared_ptr не удалит объект sp_counted, т.к. есть живая слабая ссылка (weak_count != 0).
Код weak_ptr будет выглядеть так:

template <typename T>
class weak_ptr {
    friend class shared_ptr<T>;
public:
    weak_ptr() noexcept : ptr(nullptr), counted(nullptr) {}

    weak_ptr(const weak_ptr &other) noexcept : ptr(other.ptr), counted(other.counted) {
        add_ref_weak();
    }

    weak_ptr(const shared_ptr<T> &p) noexcept : ptr(p.ptr), counted(p.counted) {
        add_ref_weak();
    }

    weak_ptr &operator=(const weak_ptr &other) noexcept {
        release_weak();

        ptr = other.ptr;
        counted = other.counted;

        add_ref_weak();

        return *this;
    }

    weak_ptr &operator=(const shared_ptr<T> &p) {
        release_weak();

        ptr = p.ptr;
        counted = p.counted;

        add_ref_weak();

        return *this;
    }

    // Пытаемся сделать shared_ptr. Для этого вызывается конструктор shared_ptr(const weak_ptr &amp;);
    // В случае невозможности создать shared_ptr возвращается пустой объект
    shared_ptr<T> lock() noexcept {
        try {
            return shared_ptr<T>(*this);
        } catch (const std::bad_weak_ptr &) {
            return shared_ptr<T>();
        }
    }

    size_t use_count() const noexcept {
        return counted != nullptr ? counted->use_count() : 0;
    }

private:
    void add_ref_weak() noexcept {
        if (counted) {
            counted->add_ref_weak();
        }
    }

    void release_weak() noexcept {
        if (counted) {
            counted->release_weak();
        }
    }

private:
    T *ptr;
    sp_counted<T> *counted;
};
В код shared_ptr добавляется конструктор shared_ptr(const weak_ptr &p):
template <typename T>
class weak_ptr;

template <typename T>
class shared_ptr {
    friend class weak_ptr<T>;
public:
    shared_ptr() noexcept : ptr(nullptr), counted(nullptr) {}

    // excaption safe конструктор
    explicit shared_ptr(T *p) {
        std::unique_ptr<T> holder(p);
        // new может кинуть исключение, и если p не передать в unique_ptr,
        // память под p потеряется
        counted = new sp_counted<T>(holder.get());
        ptr = holder.release();
    }

    ~shared_ptr() noexcept {
        release();
    }

    shared_ptr(const shared_ptr<T> &other) noexcept : ptr(other.ptr), counted(other.counted) {
        add_ref();
    }

    shared_ptr(const weak_ptr<T> &p) : ptr(p.ptr), counted(p.counted) {
        if (counted) {
            // Пытаемся увеличить счётчик ссылок объекта
            // В случае неудачи сгенерируется исключение std::bad_weak_ptr()
            counted->add_ref_lock();
        } else {
            throw std::bad_weak_ptr();
        }
    }

    shared_ptr &operator=(const shared_ptr<T> &other) noexcept {
        // Освобождаем владение предыдущим указателем
        release();

        // Выполняем присваивание
        ptr = other.ptr;
        counted = other.counted;

        // Устанавливаем владение новым указателем
        add_ref();

        // Ура! Я не забыл вернуть *this!
        return *this;
    }

    T *get() const noexcept {
        return ptr;
    }

    size_t use_count() const noexcept {
        return counted != nullptr ? counted->use_count() : 0;
    }

private:
    void add_ref() noexcept {
        if (counted) {
            counted->add_ref();
        }
    }

    void release() noexcept {
        if (counted) {
            counted->release();
        }
    }

private:
    T *ptr;
    sp_counted<T> *counted;
};

А что с потокобезопасностью?

Гарантии потокобезопасности такие же как и у shared_ptr. Но на всякий случай повторю: любая операция с weak_ptr потокобезопасна, если каждый поток хранит копию weak_ptr. При этом каждая копия может хранить сслыку на один shared_ptr. Хранить ссылки на один weak_ptr в разных потоках непотокобезопасно.

среда, 19 ноября 2014 г.

Как устроен shared_ptr

Для чего нужен

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

Как отслеживать ссылки на объект

Каждый объект shared_ptr содержит созданный в динамической памяти счётчик ссылок, который увеличивается на единицу, когда shared_ptr завладевает объектом, и уменьшается на единицу, когда shared_ptr перестаёт владеть объектом.
Код объекта счётчика ссылок может выглядеть так:

template <typename T>
class sp_counted {
public:
    explicit sp_counted(T *p) noexcept : count(1), ptr(p) {}

    void add_ref() noexcept {
        ++count;
    }

    void release() noexcept {
        if (!--count) {
            delete ptr;
            delete this;
        }
    }

    size_t use_count() const noexcept {
        return count.load();
    }

private:
    std::atomic<size_t> count;
    T *ptr;
};
При этом код shared_ptr может выглядеть так:
template <typename T>
class shared_ptr {
public:
    shared_ptr() noexcept : ptr(nullptr), counted(nullptr) {}

    // excaption safe конструктор
    explicit shared_ptr(T *p) {
        std::unique_ptr<T> holder(p);
        // new может кинуть исключение, и, если p не передать в unique_ptr,
        // память под p потеряется
        counted = new sp_counted<T>(holder.get());
        ptr = holder.release();
    }

    ~shared_ptr() noexcept {
        release();
    }

    shared_ptr(const shared_ptr &other) noexcept : ptr(other.ptr), counted(other.counted) {
        add_ref();
    }

    shared_ptr &operator=(const shared_ptr &other) noexcept {
        // Освобождаем владение предыдущим указателем
        release();

        // Выполняем присваивание
        ptr = other.ptr;
        counted = other.counted;

        // Устанавливаем владение новым указателем
        add_ref();

        // Ура! Я не забыл вернуть *this!
        return *this;
    }

    T *get() const noexcept {
        return ptr;
    }

    size_t use_count() const noexcept {
        return counted != nullptr ? counted->use_count() : 0;
    }

private:
    void add_ref() {
        if (counted) {
            counted->add_ref();
        }
    }

    void release() {
        if (counted) {
            counted->release();
        }
    }

private:
    T *ptr;
    sp_counted<T> *counted;
};

А что с потокобезопасностью?

Любая операция с shared_ptr потокобезопасна, если каждый поток хранит копию shared_ptr. При этом каждая копия может хранить указатель на один объект.
Хранить ссылки на один shared_ptr в разных потоках непотокобезопасно.

вторник, 18 ноября 2014 г.

Разработка модулей для nginx

Отличное руководство по разработке модулей для nginx - здесь