Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Иван Пономарёв — Скрытая сложность повседневной задачи: отображение табличных данных

Moscow JUG
September 21, 2017

Иван Пономарёв — Скрытая сложность повседневной задачи: отображение табличных данных

Табличный элемент управления (грид), отображающий данные из реляционной базы — один из самых распространённых компонентов пользовательского интерфейса, и все сталкивались с проблемами его производительности и удобства. Доклад покажет, что за кажущейся простотой грида скрываются интересные и не решённые до конца проблемы, требующие познаний в разных областях математики. Мы попробуем отыскать баланс между скоростью и удобством, параллельно научившись интерполяционному поиску, разобравшись в том, что же такое Collation Rules, и увидев, как можно «помогать» базе быстро извлекать нужные для нас данные. Этот доклад вошёл в 20-ку лучших на конференции JPoint-2017.

Moscow JUG

September 21, 2017
Tweet

More Decks by Moscow JUG

Other Decks in Programming

Transcript

  1. Чем мы занимаемся в КУРСе • Course Orchestra для создания

    бизнес- приложений • На Java — платформа, на Jython — код бизнес- логики • Бизнес-решения на платформе (банки, госструктуры, коммерческие организации) • PostgreSQL / MSSQL / Oracle 2
  2. Хороший грид должен • Работать быстро (не грузить БД!) •

    Работать удобно (не грузить пользователя!) • Одинаково быстро и удобно на 10 записях и на 1 млн записей. 6
  3. Подход №1: вычитать в массив • Прокрутка: доступ к «окну»

    == доступ к N-ному элементу массива —О(1) • Переход к записи: бинарный поиск в отсортированном массиве —O(log(N)) 9
  4. 11

  5. Проблемы: • Первоначальная загрузка данных – O(N) • «Тяжёлые» данные

    могут не «пролезть» в пропускную способность канала • Данные изменяются со временем – надо периодически обновлять массив 12
  6. Подход №2: Переложим задачу на СУБД? • Хорошая новость: извлечение

    по ключу — «лёгкая» операция SELECT … WHERE k >= … LIMIT … Переход к записи по ключу работать будет быстро! 13
  7. Подход №2: Переложим задачу на СУБД? Плохие новости: • Позиционирование:

    SELECT COUNT(*) WHERE k<… для определения диапазона полосы прокрутки и позиционирования. • Прокрутка: выбираем записи с N-ной: SELECT … OFFSET … LIMIT … 14
  8. Подход №2: Переложим задачу на СУБД? • Имитация ОFFSET: –

    Oracle before 12: WITH a AS (YOUR_QUERY_HERE_WITH_ORDER_BY) SELECT * FROM (SELECT a.*, ROWNUM rnum FROM a WHERE rownum <= limit) WHERE rnum >= offset ORDER BY rnum – MS SQL Server before 2012: WITH a AS (SELECT ROW_NUMBER() OVER (ORDER BY %s) AS [limit_row_number], %s FROM %s) SELECT * FROM a WHERE [limit_row_number] >= offset AND [limit_row_number] < offset + limit 15
  9. Проблемы: • COUNT и OFFSET — «дорогие» операции 16 “The

    rows skipped by an OFFSET clause still have to be computed inside the server; therefore a large OFFSET might be inefficient.” — Но чем мы ближе к концу набора, тем больше OFFSET!
  10. – А зачем нам вообще грид со всеми записями?! •

    Пагинация • Фильтрация и показ только лимитированного количества записей 18
  11. – А зачем нам вообще грид со всеми записями?! •

    Пагинация • Фильтрация и показ только лимитированного количества записей • “Load more” 19
  12. Почему нужен «честный» грид? 1. Пользователи любят «простыни» данных 2.

    Нужно видеть соседние записи 3. Переход к связанному списку 25
  13. 26

  14. Как справляются другие ребята? — С переменным успехом. • PgAdmin3

    — виснет • SQL Server Management Studio — по умолчанию ограничивает число строк • инструменты DB2, ERP Microsoft Navision (ок. 2003 г.) — сразу открывают и «крутят» любые таблицы 27
  15. Наше решение • Disclaimer: – Не «серебряная пуля» – Не

    всё просто с сортировкой и фильтрацией – Нужны правильные индексы, LIKE с осторожностью • Но: – Неплохой компромисс между скоростью и удобством 28
  16. Временное упрощение задачи (потом ограничения снимем!) 1. Первичный ключ таблицы

    состоит из одного поля k с типом INT 2. Сортировка по первичному ключу 29 CREATE TABLE test ( k INT NOT NULL PRIMARY KEY, descr VARCHAR(20) );
  17. Мысленный эксперимент 1. У 1-й записи k = 0. 2.

    У 1 000-й записи k = 10 000. 3. Записи отсортированы по k. • Вопрос: какое примерно значение k будет иметь запись с номером 500? 30
  18. Идея «Угадаем» взаимосвязь между ключом записи k и её порядковым

    номером λ при помощи линейной интерполяции? 31
  19. Снимаем ограничения • Ключ составной — нумеруем возможные комбинации так,

    чтобы описывались (большим) целым числом • Нужна сортировка — добавляем сортировочное поле к началу списка ключевых • Ключ содержит не только INT-поля — нумеруем значения (большими) целыми! 37
  20. Пример • Справочник улиц КЛАДР • 1 175 430 записей

    • ORDER BY name, code • CREATE INDEX ix_street ON kladr.street (name, code) 39
  21. Пользователь прокрутил записи… 40 изменилась позиция бегунка вычисление порядкового номера

    вычисление ключа запрос ближайшей записи БД Нумератор Интерполятор Полоса прокрутки
  22. Интерполяцией находим порядковый номер ключа 41 изменилась позиция бегунка вычисление

    порядкового номера вычисление ключа запрос ближайшей записи БД Нумератор Интерполятор Полоса прокрутки
  23. Вычисляем примерные значения полей ключа по примерному номеру 42 изменилась

    позиция бегунка вычисление порядкового номера вычисление ключа запрос ближайшей записи БД Нумератор Интерполятор Полоса прокрутки
  24. Запрашиваем записи, ближайшие к найденным примерным значениям полей 43 изменилась

    позиция бегунка вычисление порядкового номера вычисление ключа запрос ближайшей записи БД Нумератор Интерполятор Полоса прокрутки name: Мд‘Ukp ®$-'•ДЫ9uj?¦АОZ‡У›:"ZG818*$‰9Ювэ‘ code: я$зec§}0,&-»гФ4%эR SELECT ... FROM ... WHERE ("name", "code") >= (?, ?) LIMIT ...
  25. Асинхронное уточнение позиции 45 запрос ближайшей записи запрос реального номера

    записи SELECT COUNT(*) FROM kladr.street WHERE ("name", "code") < (?, ?) 586038 (А хотели 584600!)
  26. Позиционирование 47 обращение к таблице по ключу вычисление порядкового номера

    позиция бегунка (обр. интерп.) изменение позиции бегунка БД Нумератор Интерполятор Полоса прокрутки запрос реального номера записи SELECT ... FROM ... WHERE ("name", "code") >= (?, ?) LIMIT ... SELECT COUNT(*) FROM ... WHERE ("name", "code") < (?, ?)
  27. Вычисляем порядковый номер ключа 48 обращение к таблице по ключу

    вычисление порядкового номера позиция бегунка (обр. интерп.) изменение позиции бегунка БД Нумератор Интерполятор Полоса прокрутки name: Чаадаева code: 42000010000057000
  28. Интерполяцией находим примерную позицию бегунка 49 обращение к таблице по

    ключу вычисление порядкового номера позиция бегунка (обр. интерп.) изменение позиции бегунка БД Нумератор Интерполятор Полоса прокрутки
  29. Выставляем бегунок на позицию 50 обращение к таблице по ключу

    вычисление порядкового номера позиция бегунка (обр. интерп.) изменение позиции бегунка БД Нумератор Интерполятор Полоса прокрутки запрос реального номера записи
  30. • С BIT и INT всё просто — они нумеруют

    сами себя • Даты сводимы к целому типу • Составные ключи? • Строки? 53
  31. Составной ключ Если умеем работать с парами значений — —

    то можем работать и с произвольными N-ками значений 56
  32. Как отыскивать записи от составного ключа? • where (k1, k2)

    >= (K1, K2) (работает только в PostgreSQL) • where k1 > K1 or (k1 = K1 and (k2 >= K2 )) не использует индекс! • where k1 >= K1 and (k1 > K1 or (k2 >= K2 )) 57
  33. 60

  34. 62 ‘’ — 0, ‘а’ — 1, ‘б’ — ?

    2 — неправильный ответ: ‘абак’, ‘араб’, ‘арка’…
  35. Реальность: SELECT name FROM city ORDER BY name (PostgreSQL с

    настройками по умолчанию) 65
  36. Строка с точки зрения Collation Rule 67 Й о ш

    к а р - О л а 64 69 73 65 5C 6B 01 69 66 5C 01 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 01 00 00
  37. Насколько всё это быстро? JMH-замер: • 10000 ops/sec — для

    200 символов • 30000 ops/sec — для 50 символов 68
  38. А также не упомянуты… • Отработка и сортировка NULL-значений •

    Режим прокрутки на малый шаг • Анализ «качества» интерполяционных точек …и другие мелкие, но важные детали “God is in the detail.” 70
  39. Выводы 4. SQL не всемогущ. Ему можно и нужно «помогать»

    5. Помните, что сортировка строк существенно зависит от Collation. Следите за текущим Collation. 6. «Приблизительное» решение бывает намного быстрее «точного» и практически столь же хорошо 74
  40. Ссылки • Демо-пример: https://github.com/inponomarev/lyragrid-demo • Платформа C-Orchestra: http://corchestra.ru «Реализация грида»

    – https://habrahabr.ru/post/278773/ – https://habrahabr.ru/post/279083/ • «Быстро/неудобно — медленно/удобно» – https://habrahabr.ru/post/280157/ 75