Возможно, многие из вас задавались вопросом: как изменится поведение смарт-контракта, если его данные будут весить сотни мегабайт и хранить сотни тысяч или миллионы записей? Будут ли дорожать транзакции? Как это повлияет на сеть в целом? Будут ли одни типы переменных в solidity справляться с подобной задачей лучше, чем другие? Мы решили лично узнать ответы на эти вопросы и провести эксперимент в нашей приватной сети Ethereum, смоделировав описанные ситуации. Что из этого получилось читайте дальше в статье.


Описание теста


Мы хотели выяснить, как повлияет загрузка на один смарт-контракт миллиона транзакций и большого объёма данных.

Замерялись такие параметры:

  • стоимость одной транзакции в kgas;
  • длительность создания одного блока;
  • размер одного блока;

Параметры блокчейна

В нашем распоряжении имелся приватный PoA-блокчейн с двумя авторизованными пишущими узлами и одним «пассивным», с которого и отправлялись транзакции.

Все три узла были запущены на идентичных серверах:

  • Процессор: 2 Intel Xeon E5-2670 2,60 ГГц.
  • ОЗУ: 8 Гб.
  • ОС: Windows Server 2012 R2 Datacenter (64-битная).

Описание смарт-контрактов

Было создано два смарт-контракта. Один имел в качестве поля mapping bytes32 => bytes32, другой — одномерный массив bytes32. Каждый из контрактов содержал функцию, принимающую в качестве параметра значение bytes32 и сохраняющую это значение как элемент соответствующего mapping'a или массива (по сути, значение сохранялось в хранилище блокчейна).

Краткое описание контракта с mapping'ом

contract TesterMapping {
    mapping (bytes32 => bytes32) StoragedData;
 
    function Storing(bytes32 data) {
        if(StoragedData[data]!= "Y")
            StoragedData[data] = "Y";
    }
}

Краткое описание контракта с массивом

Con tract TesterArray {
    bytes32[] StoragedArray;
 
    function Storing(bytes32 data) {
        for(uint256 i = 0; i < StoragedArray.length; i++) {
            if(StoragedArray[i] == data)
                return;
        }
        StoragedArray.push(data);
    }
}

Ход и результаты тестирования


Сперва тестировался смарт-контракт с полем-mapping'ом. Раз в секунду на него посылалась транзакция со случайным 32-байтовым значением. Из-за технических проблем тестирование заняло несколько больше времени, чем планировалось, и миллионная транзакция была отправлена спустя три недели после отправки первой. Затем был протестирован смарт-контракт с массивом.

Mapping—контракт

В течение всего эксперимента ни стоимость транзакции, ни размер блока не колебались на сколько-нибудь значимые величины. Каждая транзакция обрабатывалась практически мгновенно, как первая, так и миллионная. Длительность создания блока периодически менялась от 1 до 9 секунд, но всегда симметрично заданному в генезис-блоке значению в 5 сек (если между созданием блока n и n+1 проходила одна секунда, то блок n+2 появлялся через 9 сек), то есть средняя длительность создания блока оставалось равной 5 секундам. Однако какой-то закономерности в возникновении этих флуктуаций замечено не было и, возможно, это было связано с работой нашей сети или вспомогательным программным обеспечением серверов (антивирусы и прочее).

Контракт с массивом

В данном варианте из-за наличия цикла перебора массива (для поиска совпадающих значений) стоимость одной транзакции выросла с 40 тысяч KGas до более чем 2 миллионов KGas уже за первые две тысячи транзакций. При этом продолжительность обработки одной транзакции уже за первые несколько сотен транзакций стала больше длительности создания одного блока. Из-за этого размер одного блока за приблизительно 500 транзакций упал до минимального и больше не увеличивался, так как на один блок стала приходиться в лучшем случае одна транзакция. Длительность обработки очень быстро стала настолько большой, что после отправки всего трёх тысяч транзакций «разгребание» получившейся очереди заняло около четырёх часов, при этом работа сети была бы парализована (конечно, если у отправителя в «боевой» сети хватило бы средств оплачивать такое количество столь дорогих транзакций).

Выводы



  • Размер mapping'а не влияет на быстродействие работы с ним или на стоимость транзакции (как минимум, до 1 миллиона элементов).
  • Виртуальная машина Solidity крайне неэффективна при работе с итерационными циклами.
  • Для работы с большим количеством записей лучше использовать mapping.

К сожалению, мы не нашли способа определить, какой объем данных в хранилище занимают данные конкретного смарт-контракта при использовании mapping-массива. Возможно, кто-нибудь из читателей сможет подсказать свой вариант.

Комментарии (9)


  1. Coffin
    23.04.2018 18:02

    Когда уже это исправят?
    safari OSX — take.ms/rWNTA
    chrome OSX — take.ms/1mbM8


  1. Tsvetik
    23.04.2018 18:34
    +2

    Какой объем хранилища занимает маппинг надо смотреть в исходниках клиента, который вы используете geth или parity. Конкретно изучать trie деревья, кодирование в RLP и код записи и чтения базы данных.


  1. Laney1
    23.04.2018 19:13

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


  1. Homakov
    23.04.2018 20:27

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


  1. decomeron
    23.04.2018 21:15

    Возможно, кто-нибудь из читателей сможет подсказать свой вариант.

    Миллион дадите?


  1. smile616
    24.04.2018 09:51

    А что если использовать массив вот так:

    contract TesterBoolArray {
      bool[2 ** (32 * 8)] StoragedArray;
     
      function Storing(bytes32 data) {
        uint256 i = uint256(data);
        if (!StoragedArray[i])
          StoragedArray[i] = true;
        }
      }
    }
    

    ?


    1. freeart
      24.04.2018 18:00

      удалено


  1. BoogerWooger
    24.04.2018 12:01

    Странный тест — поведение маппингов и массивов подробно описано в доке. Зачем статью из этого делать — неясно


  1. MadJackal
    24.04.2018 23:13

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