Go против Excel на сотни тысяч строк

    В этом году мы уже писали на Хабре про наш проект SmartCalls.io – визуальный конструктор звонков, созданный для бизнес-пользователей. Проект решает задачу бизнеса по массовым обзвонам клиентов: создается визуальный сценарий звонка, загружается Excel-файл с номерами телефонов и далее создается кампания по обзвону. Запускается кампания – начинается обзвон клиентов; в любой момент можно смотреть статистику, приостанавливать кампанию, подкручивать настройки. Клиенты были довольны, пока не выяснилось, что иногда надо обзванивать не просто много людей, а ОЧЕНЬ, ОЧЕНЬ много. Под катом – суть проблемы и как мы ее победили с помощью хайпового (не безосновательно) языка программирования.


    Проблема


    Изначально обработка файлов была реализована на PHP 7.1 – это был очевидный выбор, так как весь API SmartCalls был написан именно на нем. Работа с колл-листами имела одно ограничение – файл должен содержать не более 10 тысяч заполненных строк. Это ограничение было с самого начала в SmartCalls и, впрочем, не было критичным. До определенного момента.

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

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

    Решение


    Мы очень компетентны в разработке на Java – например, частично API Voximplant реализован на этом языке; также мы хорошо умеем в PHP (см. пример выше – подсказывает Капитан Очевидность). То есть мы могли быстро закрыть эту задачу, используя один из этих языков, однако мы давно думали расширить наш стек технологий, и тут как нельзя кстати мы вспомнили про Go: он достаточно быстрый (хорошо работает с памятью), многопоточный и ему не нужен рантайм, т.к. Go компилируется в исполняемый бинарник. Вдобавок можно сказать про размер контейнеров, но об этом чуть позже…

    В итоге мы написали микросервис на языке Go, который принимает листы большого размера (тестировали до 300 тысяч строк) и формата (xls, xlsx и все их разновидности). Настало время для подробностей.

    Реализация


    Когда клиент загружает в кампанию SmartCalls файл >10 тысяч строк, за него берется микросервис. Он принимает на вход указатели:

    • на файл, загруженный в S3-хранилище;
    • на кампанию, в которую этот файл нужно загрузить.

    Затем микросервис пробегает по файлу, бьет его на чанки по 10 тысяч строк (максимум для платформы) и каждый чанк в виде csv-файла загружает в S3-хранилище, делая о каждом чанке заметки в БД (путь до файла, количество строк). Каждый чанк обрабатывается и загружается в отдельном потоке, что дает дополнительный прирост в скорости выполнения.

    Для чтения Excel-файлов использовали опенсорсные библиотеки от tealeg и extrame. Хорошо, что у них не только много звезд, но еще и свежие коммиты :)

    import (
      "github.com/tealeg/xlsx"
      "github.com/extrame/xls"
      // прочие импорты
    )

    И все бы хорошо, но не обошлось без нюансов. В ходе разработки выяснилось, что xlsx и xls, созданные в разных редакторах, сильно отличаются по форматам и правилам работы с ними. Пришлось делать много тестов – OpenOffice, Excel разных версий, LibreOffice, Google Sheets, чтобы научить микросервис приводить файлы к единому виду – CSV. После того, как микросервис «прожевывает» большой файл и превращает в CSV, в работу включается API SmartCalls и уже работает с этим csv-файлом. Для микросервиса мы оставили лимит в 300 тысяч строк, так как он сильно покрывает нужды клиентов, а с бОльшими потребностями мы и вовсе не сталкивались.

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

    Вывод


    Наша команда всегда старается быстро выкатывать новые фичи/доработки, потому что мы хотим, чтобы довольные клиенты таковыми и оставались. Задача с большими файлами была не просто очередным челленджем для нас, но еще и хорошим поводом внедрить в проект Go, к которому мы давно присматривались. Помимо быстрой разработки и скорости работы, Go дает нам задел на будущее, когда мы начнем внедрять контейнеры (чтобы делать бесшовные апдейты и вот это всё), которые у этого языка весьма легковесные. Про контейнеры мы обязательно напишем отдельно, stay tuned :)
    Voximplant
    95,00
    Облачная платформа голосовой и видеотелефонии
    Поделиться публикацией

    Комментарии 34

      +2
      Вот у вас на картинке красивая схема, а про нее ни слова. Как реализовывали, сколько данных хранит?
        +1
          +1
          Это фронт, бэк более интересен. Как обрабатывается, хранится, что будет, если сделать цепочку из 150 шагов, и тд.
            0
            Да в виде JSON, а потом JSON преобразуется в JavaScript-сценарии Voximplant
        +10
        У нас есть большая экспертиза в разработке на JAVA

        Эксперти́за (от лат. expertus — опытный, сведущий) — исследование, проводимое лицом, сведущим в науке, технике, искусстве или ремесле, привлечённым по поручению заинтересованных лиц, в целях получения ответа на вопросы, требующие специальных познаний.

        Одолели «экспертизы». Извините.
          +4
          Вы правы, слово «экспертиза» нельзя использовать в значении «опыт».
          Однако так стали часто делать и я было принял это за устоявшийся отраслевой сленг, поэтому и применил в статье. Но все же это не сленг, а просто… неправильно.

          Спасибо, поправил этот фрагмент.
            +1

            В языке нет понятия правильно и неправильно. Есть только используется и не используется. И экспертиза в этом "неправильном" смысле используется давольно давно, поэтому все вы нормально написали изначачально. Даже тот, кто не любит это слово, вас сразу понял.

              0
              А я понял это так, что у них есть какие-то прикормленные эксперты, которые могут дать нужные экспертизы. Но причём тут Java я не осознал. Только вот после комментария понял, что тут просто неправильное использование правильного слова.
              0
              Я первый раз в жизни вижу слово «экспертиза» в значении «опыт». Не надо так, пожалуйста. Слово «опыт» правильное, короткое, ёмкое и полностью описывает предмет. А от «экспертиза в разработке» так и веет прыщавым junior-ом с раздутым самомнением, отвратительным вкусом, с вейпом в зубах, со смузи в руках и на каком-нибудь гироскутере.
              +2
              Хуже «экспертизы» в значении «опыт» только «функционал» в значении «функциональность».
              +7
              Не знаю чем ваши клиенты довольны, может просто количеству звонков клиентам. Но отношение к себе они такими звонками только портят.
              Банки уже достали подобной рекламой. Раньше дослушивал чтобы узнать по какому поводу, все таки человек звонит. А сейчас на первых словах сразу ясно что робот, сбрасываю. Так они снова и снова звонят, и с разных номеров.
                +2
                Кроме тупых роботизированых дозвонов клиентам с желанием что-то впарить, есть еще и автоматизация. Например, доставки или уведомлений о записи к врачу или парикмахеру. Ну и про робота: пару лет еще и не будет ясно.
                  +1
                  Кстати, круто выручает антиспам штука на андроиде. У меня почти все спам-звонки отсекаются, ну т.е они помечаются спамными и я их просто не беру, кому надо — смску напишут :)
                    +4
                    А сейчас на первых словах сразу ясно что робот, сбрасываю.


                    Когда звонит даже не оператор, а робот, и просит оставаться на линии и подождать живого оператора — это вообще за гранью добра и зла. Альфа-банк таким злоупотребляет.
                      0
                      Это криво настроенный предиктивный обзвон
                      +1
                      А как становится ясно, что робот?
                      Тоже звонят постоянно из банков, из опсосов и т.п., с разных номеров.
                      Здороваются, по имени-отчеству отбращаются, спрашивают удобно или нет.
                      На роботов вроде не похоже. Но тоже сразу понятно, что спамеры, ничего не отвечаю, даю отбой и в чёрный список, часто и номер по маске, если бомбят с диапазона колцентра.
                      Надоели ужасно.
                        0
                        Три секунды пауза после моего «алло» — робот.
                      0
                      del
                        +1
                        сколько времени ушло на разработку микросервиса? как быстро обрабатываются xlsx тысяч на сто строк? я как-то пару лет назад столкнулся с такой задачей, жавовский apache poi вообще не справлялся, жрал гигабайты памяти и думал несколько минут
                          0
                          По времени разработки – примерно 4 рабочих дня, включая тесты/обкатку.
                          Про тайминг смогу ответить попозже, если вы не против :) В понедельник вас устроит?
                            0

                            Да, конечно =)

                              0
                              Взяли максимум, прогнали в очередной раз – xlsx размером 300.000 строк читается и обходится за 18 секунд.
                              P.S. прошу прощения, что не смог ответить в понедельник.
                                0
                                хм, очень неплохо, у меня примерно теже результаты на жаве получились
                          +4
                          Название статьи не подтвердили. Нужен бенчмарк go и excel.
                            0
                            у меня стояла задача обзванивать должников, списки по 3- 5 тысяч позиций, тоже сначала было на php
                            Но почему то от екселя сразу отказались.
                            Было сделано два варианта:
                            1 PHP скрипт вызывает процедуру на биллиноговом MSSQL, получает список и звонит.
                            2 ПОльзователь в интерфейсе задает параметры — по параметрам обращение к базе и вывод списка, пользователь удаляет лишние строки и запускает обзвон.
                              +1
                              … и ему не нужен рантайм, т.к. Go компилируется в исполняемый бинарник.

                              Ну нельзя же так. Такая успешная компания, делает такие замечательные ляпы.
                              Но Go одобряю. Лучше, чем C, C++, python.
                                0
                                Строго говоря, вы конечно правы, т.к. формально у всех есть рантайм)) Имелось в виду скорее то, что GO не требует тяжеловесный рантайм (привет java и python), он скомпилирован статически, минимум зависимостей от ОС.

                                P.S. рады, что вы одобрили наш выбор.
                                0
                                А не могли бы вы объяснить, чем обусловлен выбор именно Golang, а не используемых у вас в компании Java и PHP, для данной задачи?
                                Ещё очень интересно было бы понять, почему вы решили делать доработку именно в формате микросервиса, а не правки в существующий сервис, что по сути было бы просто расширением ограничения с 10к до 300к строк?
                                  0
                                  Потому что PHP не справлялся с объемами. А при в общем равной скорости обработки листов JAVA и GO, контейнеры на GO весят раз в 10 меньше, чем JAVA; с учетом, что мы планируем в дальнейшем использовать Kubernetes Engine, это вполне оправданный выбор. По этой же причине сделали в формате микросервиса.
                                  0
                                  А у вас не возникало проблем с extrame/xls? Она портит данные при чтении. Или для вашей задачи это допустимо?
                                    0
                                    На этапе разработки – возникали :) поэтому постили мэйнтэйнерам баги, получали ответы. Мы бы не стали выкатывать в прод решение, которое портит данные.
                                      0

                                      На самом деле extrame/xls все ещё портит данные при чтении (иногда). Я пытался решить проблему в PR#50, но пока на 100% решить не удалось. Там вроде бы все не так сложно и есть даже куда подсмотреть «как должно быть» — в libxls на c, просто ни у кого руки не дошли пока.
                                      Возможно на ваших файлах проблема не возникает, либо вы просто не замечаете потери одной-двух строк из десятков тысяч.
                                      Попробуйте открыть ваш большой xls файл в Excel и сохранить как xlsx, а затем прочитайте xls через extrame, а xlsx через tealeg/xlsx и сравните каждую ячейку. Возможно увидите отличия.

                                    0
                                    А старые форматы excel (BIFF 4) корректно парсятся?
                                      0
                                      BIFF4 это Excel 4.0, 1992 года выпуска. Такие старые форматы мы не поддерживаем, потому что бизнес их не использует.

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

                                    Самое читаемое