1 of 77

Приблизительный план

  • 19:00 – 19:50 Гриды: скрытая сложность (Иван Пономарёв)
  • 19:50 – 20:00 Q&A
  • 20:00 – 20:20 Celesta и Flute: что это и зачем? (Николай Поташников)
  • 20:20 – 20:35 Чай с пирожками
  • 20:35 – 21:35 Создание бизнес-логики на Celesta и Flute (Иван Пономарёв)
  • 21:35 – 22:00 Q&A

1

2 of 77

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

Иван Пономарёв, КУРС/МФТИ

@inponomarev

2

3 of 77

Чем мы занимаемся в КУРСе

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

3

4 of 77

Все знают, что такое грид

4

5 of 77

Грид – это прокрутка…

5

6 of 77

Грид – это позиционирование…

6

7 of 77

Хороший грид должен

  • Работать быстро (не грузить БД!)
  • Работать удобно (не грузить пользователя!)
  • Одинаково быстро и удобно на 10 записях �и на 1 млн записей.

7

8 of 77

��

8

— И разве нет готовых решений?

— А разве это сложно?

9 of 77

и вычитать данные таблицы в массив

9

Подход №1: просто взять

10 of 77

Подход №1: вычитать в массив

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

10

11 of 77

Проблемы:

  • Первоначальная загрузка данных – O(N)

11

12 of 77

12

13 of 77

Проблемы:

  • Первоначальная загрузка данных – O(N)
  • «Тяжёлые» данные могут не «пролезть» �в пропускную способность канала
  • Данные изменяются со временем – надо периодически обновлять массив

13

14 of 77

Подход №2: �Переложим задачу на СУБД?

  • Хорошая новость: извлечение по ключу — «лёгкая» операция

SELECTWHERE k >= … LIMIT

Переход к записи по ключу работать будет быстро!

14

15 of 77

Подход №2: �Переложим задачу на СУБД?

Плохие новости:

  • Позиционирование:

SELECT COUNT(*) WHERE k<… для определения диапазона полосы прокрутки и позиционирования.

  • Прокрутка: выбираем записи с N-ной:

SELECTOFFSETLIMIT 

15

16 of 77

Подход №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

16

17 of 77

Проблемы:

  • COUNT и OFFSET — «дорогие» операции

17

“The rows skipped by an OFFSET clause still have to be computed inside the server; therefore a large OFFSET might be inefficient.”

— Но чем мы ближе к концу набора, тем больше OFFSET!

18 of 77

– А зачем нам вообще грид со всеми записями?!

  • Пагинация

18

19 of 77

– А зачем нам вообще грид со всеми записями?!

  • Пагинация
  • Фильтрация и показ только лимитированного количества записей

19

20 of 77

– А зачем нам вообще грид со всеми записями?!

  • Пагинация
  • Фильтрация и показ только лимитированного количества записей
  • “Load more”

20

21 of 77

Бывает и так:

21

А где полоса прокрутки?

(Обсуждение темы:

https://habrahabr.ru/post/280157/)

22 of 77

— Может, строить «честный грид» �и не надо?

22

23 of 77

— Надо, Федя!

23

24 of 77

Почему нужен «честный» грид?

  1. Пользователи любят «простыни» данных

24

25 of 77

Почему нужен «честный» грид?

  1. Пользователи любят «простыни» данных
  2. Нужно видеть соседние записи

25

26 of 77

Почему нужен «честный» грид?

  1. Пользователи любят «простыни» данных
  2. Нужно видеть соседние записи
  3. Переход к связанному списку

26

27 of 77

27

28 of 77

Как справляются другие ребята?

— С переменным успехом.

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

28

29 of 77

Наше решение

  • Disclaimer:
    • Не «серебряная пуля»
    • Не всё просто с сортировкой и фильтрацией
    • Нужны правильные индексы, LIKE с осторожностью

  • Но:
    • Неплохой компромисс между скоростью и удобством

29

30 of 77

Временное упрощение задачи�(потом ограничения снимем!)

  1. Первичный ключ таблицы состоит �из одного поля k с типом INT
  2. Сортировка по первичному ключу

30

CREATE TABLE test (

k INT NOT NULL PRIMARY KEY,

descr VARCHAR(20)

);

31 of 77

Мысленный эксперимент

  1. У 1-й записи k = 0.
  2. У 1 000-й записи k = 10 000.
  3. Записи отсортированы по k.
  4. Вопрос: какое примерно значение k�будет иметь запись с номером 500? �

31

 

32 of 77

Идея

«Угадаем» взаимосвязь между ключом записи k�и её порядковым номером λ при помощи линейной интерполяции?

32

33 of 77

Насколько хороша линейная интерполяция? (50 из 200)

33

порядковый номер записи

значение ключа

34 of 77

Случай 10 из 200 — похуже…

34

значение ключа

порядковый номер записи

35 of 77

Вероятность наличия �ровно λ записей с ключом <k

35

36 of 77

Теоретическая оценка границ, �мат. ожидания и СКО

36

37 of 77

Неравномерное распределение ключей�— кусочная интерполяция!

37

38 of 77

Снимаем ограничения

  • Ключ составной

— нумеруем возможные комбинации так, чтобы описывались (большим) целым числом

  • Нужна сортировка

— добавляем сортировочное поле к началу списка ключевых

  • Ключ содержит не только INT-поля

— нумеруем значения (большими) целыми!

38

39 of 77

39

интерполяционная

таблица

Компоненты алгоритма

нумератор

интер-�полятор

генератор�запросов

40 of 77

Пример

  • Справочник улиц КЛАДР
  • 1 175 430 записей
  • ORDER BY name, code
  • CREATE INDEX ix_street ON kladr.street (name, code)

40

41 of 77

Пользователь прокрутил записи…

41

изменилась�позиция бегунка

вычисление

порядкового номера

вычисление

ключа

запрос

ближайшей записи

БД

Нумератор

Интерполятор

Полоса �прокрутки

 

42 of 77

Интерполяцией находим порядковый номер ключа

42

изменилась�позиция бегунка

вычисление

порядкового номера

вычисление

ключа

запрос

ближайшей записи

БД

Нумератор

Интерполятор

Полоса �прокрутки

 

 

43 of 77

Вычисляем примерные значения �полей ключа по примерному номеру

43

изменилась�позиция бегунка

вычисление

порядкового номера

вычисление

ключа

запрос

ближайшей записи

БД

Нумератор

Интерполятор

Полоса �прокрутки

 

 

44 of 77

Запрашиваем записи, ближайшие �к найденным примерным значениям полей

44

изменилась�позиция бегунка

вычисление

порядкового номера

вычисление

ключа

запрос

ближайшей записи

БД

Нумератор

Интерполятор

Полоса �прокрутки

name: Мд‘Ukp ®$-'•ДЫ9uj?¦АОZ‡У›:"ZG818*$‰9Ювэ‘

code: я$зec§}0,&-»гФ4%эR

SELECT ... FROM ... WHERE

("name", "code") >= (?, ?) LIMIT ...

45 of 77

Прокрутка в реальном времени

45

46 of 77

Асинхронное уточнение позиции

46

запрос

ближайшей записи

запрос реального�номера записи

SELECT COUNT(*) FROM kladr.street WHERE ("name", "code") < (?, ?)

586038

(А хотели 584600!)

47 of 77

Позиционирование

47

48 of 77

Позиционирование

48

обращение �к таблице по ключу

вычисление

порядкового номера

позиция бегунка

(обр. интерп.)

изменение позиции�бегунка

БД

Нумератор

Интерполятор

Полоса �прокрутки

запрос реального�номера записи

SELECT ... FROM ... WHERE

("name", "code") >= (?, ?) LIMIT ...

SELECT COUNT(*) FROM ...

WHERE ("name", "code") < (?, ?)

49 of 77

Вычисляем порядковый номер ключа

49

обращение �к таблице по ключу

вычисление

порядкового номера

позиция бегунка

(обр. интерп.)

изменение позиции�бегунка

БД

Нумератор

Интерполятор

Полоса �прокрутки

 

name: Чаадаева

code: 42000010000057000

50 of 77

Интерполяцией находим примерную позицию бегунка

50

обращение �к таблице по ключу

вычисление

порядкового номера

позиция бегунка

(обр. интерп.)

изменение позиции�бегунка

БД

Нумератор

Интерполятор

Полоса �прокрутки

 

 

51 of 77

Выставляем бегунок на позицию

51

обращение �к таблице по ключу

вычисление

порядкового номера

позиция бегунка

(обр. интерп.)

изменение позиции�бегунка

БД

Нумератор

Интерполятор

Полоса �прокрутки

 

 

запрос реального�номера записи

52 of 77

Осталось разобраться в одном вопросе…

Как работает нумератор?

52

53 of 77

Функция нумератора

  •  

53

54 of 77

  • С BIT и INT всё просто — они нумеруют сами себя
  • Даты сводимы к целому типу

  • Составные ключи?
  • Строки?

54

55 of 77

Составной ключ/сортировка:�ORDER BY X, Y

  •  

55

56 of 77

Составной ключ

Если умеем работать �с парами значений —

56

57 of 77

Составной ключ

Если умеем работать �с парами значений —

�— то можем работать �и с произвольными N-ками значений

57

58 of 77

Как отыскивать записи �от составного ключа?

  • where (k1, k2) >= (K1, K2)

(работает только в PostgreSQL)

  • where k1 > K1 or (k1 = K1 and (k2 >= K2))

не использует индекс!

  • where k1 >= K1 and (k1 > K1 or (k2 >= K2))

58

59 of 77

Нумерация строк

59

60 of 77

Интерполяция строк

60

61 of 77

 

61

 

62 of 77

 

Пустая строка, строка из одной буквы, из двух букв…

62

 

 

63 of 77

 

‘’ — 0, ‘а’ — 1, ‘б’ — ?

2 — неправильный ответ: ‘абак’, ‘араб’, ‘арка’…

63

 

64 of 77

 

  •  

64

  • Общая формула:
  • Постаравшись, можно найти алгоритм обратного вычисления строки

65 of 77

Ничего не упустили?.. �Пишем тест:

ИЖЕВСК 297544850130486

ИРКУТСК 309090873152681

ЙОШКАР-ОЛА 343626219328684

66 of 77

Реальность: �SELECT name FROM city ORDER BY name�(PostgreSQL с настройками по умолчанию)

66

67 of 77

Правила сортировки (collation rules)

Результат «подгонки» правил к PostgreSQL:

…<в,В<г,Г<д,Д<е,Е;ё,Ё<ж,Ж<з,З<и,И;й,Й<к,К<л,Л…

67

java.text.RuleBasedCollator extends java.text.Collator

java.text.CollationKey

68 of 77

Строка с точки зрения Collation Rule

68

Й

о

ш

к

а

р

-

О

л

а

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

69 of 77

Насколько всё это быстро?

JMH-замер:

  • 10000 ops/sec — для 200 символов
  • 30000 ops/sec — для 50 символов

69

70 of 77

Первоначальное заполнение интерполяционной таблицы

70

71 of 77

А также не упомянуты…

  • Отработка и сортировка NULL-значений
  • Режим прокрутки на малый шаг
  • Анализ «качества» интерполяционных точек

…и другие мелкие, но важные детали

“God is in the detail.”

71

72 of 77

Выводы

1. Создавать фреймворк — дело трудное�и неблагодарное

72

73 of 77

Выводы

2. Всегда помните�про О-оценку сложности алгоритмов

73

74 of 77

Выводы

3. По возможности, избегайте COUNT �и OFFSET

74

75 of 77

Выводы

  1. SQL не всемогущ. Ему можно и нужно «помогать»
  2. Помните, что сортировка строк существенно зависит от Collation. Следите за текущим Collation.
  3. «Приблизительное» решение бывает намного быстрее «точного» и практически столь же хорошо

75

76 of 77

Ссылки

  • Демо-пример: 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/

76

77 of 77

Вопросы?

77