© 2015 by Markus Winand
iStockPhoto/Mitshu
From Backend to Frontend
When database optimization affects the full stack
@MarkusWinand
@SQLPerfTips
@ModernSQL
© 2015 by Markus Winand
Full Stack, Really?
© 2015 by Markus Winand
Full Stack, Really?
© 2015 by Markus Winand
What Users Want are Used to
© 2015 by Markus Winand
What Users Want are Used to
Let’s
click here
© 2015 by Markus Winand
User Clicks on Page 2
Gimme
Page 2!
Gimme
Rows
11 to 20
SQL for “Gimme Rows 11 to 20”
se!e"# *
$%om news
whe%e #op&" ' 1234
o%de% b( da#e des", &d des"
o!!se" 10
#$m$" 10
Inside the Database: Worst Case
L&m&# (a"#ua! %ows'10)
-> So%# (a%"ua# &ows'20)
So%# Me#hod: "op-N heapso&" Memo&(: 19)B
-> B&#map Heap S"an (a"#ua! %ows'10000)
Re"he") *ond: (#op&" ' 1234)
-> B&#map Index S"an (a"#ua! %ows'10000)
Index *ond: (#op&" ' 1234)
Inside the Database: Worst Case
L&m&# (a"#ua! %ows'10)
-> So%# (a%"ua# &ows'30)
So%# Me#hod: "op-N heapso&" Memo&(: 20)B
-> B&#map Heap S"an (a"#ua! %ows'10000)
Re"he") *ond: (#op&" ' 1234)
-> B&#map Index S"an (a"#ua! %ows'10000)
Index *ond: (#op&" ' 1234)
Inside the Database: Worst Case
L&m&# (a"#ua! %ows'10)
-> So%# (a%"ua# &ows'40)
So%# Me#hod: "op-N heapso&" Memo&(: 22)B
-> B&#map Heap S"an (a"#ua! %ows'10000)
Re"he") *ond: (#op&" ' 1234)
-> B&#map Index S"an (a"#ua! %ows'10000)
Index *ond: (#op&" ' 1234)
Inside the Database: Worst Case
L&m&# (a"#ua! %ows'10)
-> So%# (a%"ua# &ows'10000)
So%# Me#hod: ex"e&na# me&*e D$s): 1200)B
-> B&#map Heap S"an (a"#ua! %ows'10000)
Re"he") *ond: (#op&" ' 1234)
-> B&#map Index S"an (a"#ua! %ows'10000)
Index *ond: (#op&" ' 1234)
Inside the Database: Worst Case
Speed depends on the number of matched rows
and the page fetched.
Fetching the last page
can take considerably
longer than fetching
the first page.
Improvement 1: Indexed order by
se!e"# *
$%om news
whe%e #op&" ' 1234
o%de% b( da#e des", &d des"
o$$se# 10
!&m&# 10;
%&ea"e $ndex on news ("op$%, da"e, $d)
A single index to support the whe%e and o%de% b(
clauses.
Improvement 1: Indexed order by
L&m&# (a%"ua# &ows'10)
-> Index S"an Ba")wa%d (a%"ua# &ows'10)
Index *ond: (#op&" ' 0)
Improvement 1: Indexed order by
L&m&# (a%"ua# &ows'10)
-> Index S"an Ba")wa%d (a%"ua# &ows'20)
Index *ond: (#op&" ' 0)
Improvement 1: Indexed order by
L&m&# (a%"ua# &ows'10)
-> Index S"an Ba")wa%d (a%"ua# &ows'30)
Index *ond: (#op&" ' 0)
Improvement 1: Indexed order by
L&m&# (a%"ua# &ows'10)
-> Index S"an Ba")wa%d (a%"ua# &ows'40)
Index *ond: (#op&" ' 0)
Improvement 1: Indexed order by
L&m&# (a"#ua! %ows'10)
-> So%# (a%"ua# &ows'10000)
So%# Me#hod: ex"e&na# me&*e D$s): 1200)B
-> B&#map Heap S"an (a"#ua! %ows'10000)
Re"he") *ond: (#op&" ' 1234)
-> B&#map Index S"an (a"#ua! %ows'10000)
Index *ond: (#op&" ' 1234)
Improvement 1: Indexed order by
Fetching the first page is not affected by the
Base-Set size!
Fetching the next page
is also faster.
However, the database
might revert to the other
execution plan when
browsing to the end.
© 2013 by Markus Winand
It’s not just about performance
© 2015 by Markus Winand
Offsets Yield Unstable Results
© 2015 by Markus Winand
Offsets Yield Unstable Results
© 2015 by Markus Winand
Offsets Yield Unstable Results
© 2013 by Markus Winand
Why don’t we ask for
the next rows after...
© 2015 by Markus Winand
Asking for “Next After” in SQL
© 2015 by Markus Winand
Asking for “Next After” in SQL
© 2015 by Markus Winand
Asking for “Next After” in SQL
© 2015 by Markus Winand
Asking for “Next After” in SQL
© 2015 by Markus Winand
Asking for “Next After” in SQL
© 2015 by Markus Winand
Asking for “Next After” in SQL
SQL For “Next After” (Key-Set Pagination)
se!e"# *
$%om news
whe%e #op&" ' 1234
and (da"e, $d) < (?, ?)
o%de% b( da#e des", &d des"
!&m&# 10 Look Ma,
no offset!
Key-Set Pagination: Worst Case
Key-Set Pagination: Worst Case
L&m&# (a%"ua# &ows'10)
-> So%# (a%"ua# &ows'10)
So%# Me#hod: "op-N heapso&" Memo&(: 18)B
-> B&#map Heap S"an (&ows'10000)
Re"he") *ond: (#op&" ' 1234)
-> B&#map Index S"an (&ows'10000)
Index *ond: (#op&" ' 1234)
Key-Set Pagination: Worst Case
L&m&# (a"#ua! %ows'10)
-> So%# (a%"ua# &ows'10)
So%# Me#hod: "op-N heapso&" Memo&(: 18)B
-> B&#map Heap S"an (a%"ua# &ows'9990)
Rows Remo+ed b( F$#"e&: 10 (new &n 9.2)
-> B&#map Index S"an (a%"ua# &ows'10000)
Index *ond: (#op&" ' 1234)
Key-Set Pagination: Worst Case
L&m&# (a"#ua! %ows'10)
-> So%# (a%"ua# &ows'10)
So%# Me#hod: "op-N heapso&" Memo&(: 18)B
-> B&#map Heap S"an (a%"ua# &ows'9980)
Rows Remo+ed b( F$#"e&: 20 (new &n 9.2)
-> B&#map Index S"an (a%"ua# &ows'10000)
Index *ond: (#op&" ' 1234)
Key-Set Pagination: Worst Case
L&m&# (a"#ua! %ows'10)
-> So%# (a%"ua# &ows'10)
So%# Me#hod: "op-N heapso&" Memo&(: 18)B
-> B&#map Heap S"an (a%"ua# &ows'9970)
Rows Remo+ed b( F$#"e&: 30 (new &n 9.2)
-> B&#map Index S"an (a%"ua# &ows'10000)
Index *ond: (#op&" ' 1234)
Key-Set Pagination: Worst Case
L&m&# (a"#ua! %ows'10)
-> So%# (a%"ua# &ows'10)
So%# Me#hod: "op-N heapso&" Memo&(: 18)B
-> B&#map Heap S"an (a%"ua# &ows'10)
Rows Remo+ed b( F$#"e&: 9990 (new &n 9.2)
-> B&#map Index S"an (a%"ua# &ows'10000)
Index *ond: (#op&" ' 1234)
Key-Set Pagination w/o Index for order by
Always needs to retrieve the full base set, but the
top-n sort buffer needs
to hold only 10 rows.
The response time remains
the same even when
browsing to the last page.
And the memory footprint
is very low!
Key-Set Pagination: Best Case
se!e"# *
$%om news
whe%e #op&" ' 1234
and (da#e, &d) < (?, ?)
o%de% b( da#e des", &d des"
!&m&# 10;
%&ea"e $ndex on news ("op$%, da"e, $d)
Key-Set Pagination: Best Case
L&m&# (a%"ua# &ows'10)
-> Index S"an Ba")wa%d (a%"ua# &ows'10)
Index *ond: (#op&" ' 1234)
Key-Set Pagination: Best Case
L&m&# (a%"ua# &ows'10)
-> Index S"an Ba")wa%d (a%"ua# &ows'10)
Index *ond: ((#op&" ' 1234)
AND (ROW(d#, &d) < ROW(‘...’, 23456)))
Key-Set Pagination: Best Case
L&m&# (a%"ua# &ows'10)
-> Index S"an Ba")wa%d (a%"ua# &ows'10)
Index *ond: ((#op&" ' 1234)
AND (ROW(d#, &d) < ROW(‘...’, 34567)))
Key-Set Pagination: Best Case
L&m&# (a%"ua# &ows'10)
-> Index S"an Ba")wa%d (a%"ua# &ows'10)
Index *ond: ((#op&" ' 1234)
AND (ROW(d#, &d) < ROW(‘...’, 45678)))
Key-Set Pagination: Best Case
L&m&# (a%"ua# &ows'10)
-> Index S"an Ba")wa%d (a%"ua# &ows'10)
Index *ond: ((#op&" ' 1234)
AND (ROW(d#, &d) < ROW(‘...’, 56789)))
Key-Set Pagination: Best Case
Successively browsing back doesn’t slow down.
Neither the size of the
base set(*) nor the fetched
page number affects the
response time.
(*) the index tree depth still affects the response time.
ComparisonOffsetKey-Set
Worst Case Best Case
© 2015 by Markus Winand
Too good to be true?
Key-Set Pagination has serious limitations:
‣You cannot directly navigate to arbitrary pages
‣because you need the values from the previous page
‣Bi-directional navigation is possible but tedious
‣you need to reverse the o%de% b( direction and RV comparison
‣Tooling...???
© 2015 by Markus Winand
Required Tooling for Key-Set Pagination
© 2015 by Markus Winand
Required Tooling for Key-Set Pagination
Proper Row-
Values Support.
Don’t use
OFFSET.NoSQL is
also
Also for
NoSQL
© 2015 by Markus Winand
Required Tooling for Key-Set Pagination
Proper Row-
Values Support.
Don’t use
OFFSET.
Offer
Infinite Scrolling.
Don’t think in
pages.
Ask the right
question.
OFFSET is EVIL
http://use-the-index-luke.com/no-offset
© 2015 by Markus Winand
About Markus Winand
Tuning developers for
high SQL performance
Training & co (one-man show):
winand.at
Geeky blog:
use-the-index-luke.com
Author of:
SQL Performance Explained

Backend to Frontend: When database optimization affects the full stack

  • 1.
    © 2015 byMarkus Winand iStockPhoto/Mitshu From Backend to Frontend When database optimization affects the full stack @MarkusWinand @SQLPerfTips @ModernSQL
  • 2.
    © 2015 byMarkus Winand Full Stack, Really?
  • 3.
    © 2015 byMarkus Winand Full Stack, Really?
  • 4.
    © 2015 byMarkus Winand What Users Want are Used to
  • 5.
    © 2015 byMarkus Winand What Users Want are Used to Let’s click here
  • 6.
    © 2015 byMarkus Winand User Clicks on Page 2 Gimme Page 2! Gimme Rows 11 to 20
  • 7.
    SQL for “GimmeRows 11 to 20” se!e"# * $%om news whe%e #op&" ' 1234 o%de% b( da#e des", &d des" o!!se" 10 #$m$" 10
  • 8.
    Inside the Database:Worst Case L&m&# (a"#ua! %ows'10) -> So%# (a%"ua# &ows'20) So%# Me#hod: "op-N heapso&" Memo&(: 19)B -> B&#map Heap S"an (a"#ua! %ows'10000) Re"he") *ond: (#op&" ' 1234) -> B&#map Index S"an (a"#ua! %ows'10000) Index *ond: (#op&" ' 1234)
  • 9.
    Inside the Database:Worst Case L&m&# (a"#ua! %ows'10) -> So%# (a%"ua# &ows'30) So%# Me#hod: "op-N heapso&" Memo&(: 20)B -> B&#map Heap S"an (a"#ua! %ows'10000) Re"he") *ond: (#op&" ' 1234) -> B&#map Index S"an (a"#ua! %ows'10000) Index *ond: (#op&" ' 1234)
  • 10.
    Inside the Database:Worst Case L&m&# (a"#ua! %ows'10) -> So%# (a%"ua# &ows'40) So%# Me#hod: "op-N heapso&" Memo&(: 22)B -> B&#map Heap S"an (a"#ua! %ows'10000) Re"he") *ond: (#op&" ' 1234) -> B&#map Index S"an (a"#ua! %ows'10000) Index *ond: (#op&" ' 1234)
  • 11.
    Inside the Database:Worst Case L&m&# (a"#ua! %ows'10) -> So%# (a%"ua# &ows'10000) So%# Me#hod: ex"e&na# me&*e D$s): 1200)B -> B&#map Heap S"an (a"#ua! %ows'10000) Re"he") *ond: (#op&" ' 1234) -> B&#map Index S"an (a"#ua! %ows'10000) Index *ond: (#op&" ' 1234)
  • 12.
    Inside the Database:Worst Case Speed depends on the number of matched rows and the page fetched. Fetching the last page can take considerably longer than fetching the first page.
  • 13.
    Improvement 1: Indexedorder by se!e"# * $%om news whe%e #op&" ' 1234 o%de% b( da#e des", &d des" o$$se# 10 !&m&# 10; %&ea"e $ndex on news ("op$%, da"e, $d) A single index to support the whe%e and o%de% b( clauses.
  • 14.
    Improvement 1: Indexedorder by L&m&# (a%"ua# &ows'10) -> Index S"an Ba")wa%d (a%"ua# &ows'10) Index *ond: (#op&" ' 0)
  • 15.
    Improvement 1: Indexedorder by L&m&# (a%"ua# &ows'10) -> Index S"an Ba")wa%d (a%"ua# &ows'20) Index *ond: (#op&" ' 0)
  • 16.
    Improvement 1: Indexedorder by L&m&# (a%"ua# &ows'10) -> Index S"an Ba")wa%d (a%"ua# &ows'30) Index *ond: (#op&" ' 0)
  • 17.
    Improvement 1: Indexedorder by L&m&# (a%"ua# &ows'10) -> Index S"an Ba")wa%d (a%"ua# &ows'40) Index *ond: (#op&" ' 0)
  • 18.
    Improvement 1: Indexedorder by L&m&# (a"#ua! %ows'10) -> So%# (a%"ua# &ows'10000) So%# Me#hod: ex"e&na# me&*e D$s): 1200)B -> B&#map Heap S"an (a"#ua! %ows'10000) Re"he") *ond: (#op&" ' 1234) -> B&#map Index S"an (a"#ua! %ows'10000) Index *ond: (#op&" ' 1234)
  • 19.
    Improvement 1: Indexedorder by Fetching the first page is not affected by the Base-Set size! Fetching the next page is also faster. However, the database might revert to the other execution plan when browsing to the end.
  • 20.
    © 2013 byMarkus Winand It’s not just about performance
  • 21.
    © 2015 byMarkus Winand Offsets Yield Unstable Results
  • 22.
    © 2015 byMarkus Winand Offsets Yield Unstable Results
  • 23.
    © 2015 byMarkus Winand Offsets Yield Unstable Results
  • 24.
    © 2013 byMarkus Winand Why don’t we ask for the next rows after...
  • 25.
    © 2015 byMarkus Winand Asking for “Next After” in SQL
  • 26.
    © 2015 byMarkus Winand Asking for “Next After” in SQL
  • 27.
    © 2015 byMarkus Winand Asking for “Next After” in SQL
  • 28.
    © 2015 byMarkus Winand Asking for “Next After” in SQL
  • 29.
    © 2015 byMarkus Winand Asking for “Next After” in SQL
  • 30.
    © 2015 byMarkus Winand Asking for “Next After” in SQL
  • 31.
    SQL For “NextAfter” (Key-Set Pagination) se!e"# * $%om news whe%e #op&" ' 1234 and (da"e, $d) < (?, ?) o%de% b( da#e des", &d des" !&m&# 10 Look Ma, no offset!
  • 32.
  • 33.
    Key-Set Pagination: WorstCase L&m&# (a%"ua# &ows'10) -> So%# (a%"ua# &ows'10) So%# Me#hod: "op-N heapso&" Memo&(: 18)B -> B&#map Heap S"an (&ows'10000) Re"he") *ond: (#op&" ' 1234) -> B&#map Index S"an (&ows'10000) Index *ond: (#op&" ' 1234)
  • 34.
    Key-Set Pagination: WorstCase L&m&# (a"#ua! %ows'10) -> So%# (a%"ua# &ows'10) So%# Me#hod: "op-N heapso&" Memo&(: 18)B -> B&#map Heap S"an (a%"ua# &ows'9990) Rows Remo+ed b( F$#"e&: 10 (new &n 9.2) -> B&#map Index S"an (a%"ua# &ows'10000) Index *ond: (#op&" ' 1234)
  • 35.
    Key-Set Pagination: WorstCase L&m&# (a"#ua! %ows'10) -> So%# (a%"ua# &ows'10) So%# Me#hod: "op-N heapso&" Memo&(: 18)B -> B&#map Heap S"an (a%"ua# &ows'9980) Rows Remo+ed b( F$#"e&: 20 (new &n 9.2) -> B&#map Index S"an (a%"ua# &ows'10000) Index *ond: (#op&" ' 1234)
  • 36.
    Key-Set Pagination: WorstCase L&m&# (a"#ua! %ows'10) -> So%# (a%"ua# &ows'10) So%# Me#hod: "op-N heapso&" Memo&(: 18)B -> B&#map Heap S"an (a%"ua# &ows'9970) Rows Remo+ed b( F$#"e&: 30 (new &n 9.2) -> B&#map Index S"an (a%"ua# &ows'10000) Index *ond: (#op&" ' 1234)
  • 37.
    Key-Set Pagination: WorstCase L&m&# (a"#ua! %ows'10) -> So%# (a%"ua# &ows'10) So%# Me#hod: "op-N heapso&" Memo&(: 18)B -> B&#map Heap S"an (a%"ua# &ows'10) Rows Remo+ed b( F$#"e&: 9990 (new &n 9.2) -> B&#map Index S"an (a%"ua# &ows'10000) Index *ond: (#op&" ' 1234)
  • 38.
    Key-Set Pagination w/oIndex for order by Always needs to retrieve the full base set, but the top-n sort buffer needs to hold only 10 rows. The response time remains the same even when browsing to the last page. And the memory footprint is very low!
  • 39.
    Key-Set Pagination: BestCase se!e"# * $%om news whe%e #op&" ' 1234 and (da#e, &d) < (?, ?) o%de% b( da#e des", &d des" !&m&# 10; %&ea"e $ndex on news ("op$%, da"e, $d)
  • 40.
    Key-Set Pagination: BestCase L&m&# (a%"ua# &ows'10) -> Index S"an Ba")wa%d (a%"ua# &ows'10) Index *ond: (#op&" ' 1234)
  • 41.
    Key-Set Pagination: BestCase L&m&# (a%"ua# &ows'10) -> Index S"an Ba")wa%d (a%"ua# &ows'10) Index *ond: ((#op&" ' 1234) AND (ROW(d#, &d) < ROW(‘...’, 23456)))
  • 42.
    Key-Set Pagination: BestCase L&m&# (a%"ua# &ows'10) -> Index S"an Ba")wa%d (a%"ua# &ows'10) Index *ond: ((#op&" ' 1234) AND (ROW(d#, &d) < ROW(‘...’, 34567)))
  • 43.
    Key-Set Pagination: BestCase L&m&# (a%"ua# &ows'10) -> Index S"an Ba")wa%d (a%"ua# &ows'10) Index *ond: ((#op&" ' 1234) AND (ROW(d#, &d) < ROW(‘...’, 45678)))
  • 44.
    Key-Set Pagination: BestCase L&m&# (a%"ua# &ows'10) -> Index S"an Ba")wa%d (a%"ua# &ows'10) Index *ond: ((#op&" ' 1234) AND (ROW(d#, &d) < ROW(‘...’, 56789)))
  • 45.
    Key-Set Pagination: BestCase Successively browsing back doesn’t slow down. Neither the size of the base set(*) nor the fetched page number affects the response time. (*) the index tree depth still affects the response time.
  • 46.
  • 47.
    © 2015 byMarkus Winand Too good to be true? Key-Set Pagination has serious limitations: ‣You cannot directly navigate to arbitrary pages ‣because you need the values from the previous page ‣Bi-directional navigation is possible but tedious ‣you need to reverse the o%de% b( direction and RV comparison ‣Tooling...???
  • 48.
    © 2015 byMarkus Winand Required Tooling for Key-Set Pagination
  • 49.
    © 2015 byMarkus Winand Required Tooling for Key-Set Pagination Proper Row- Values Support. Don’t use OFFSET.NoSQL is also Also for NoSQL
  • 50.
    © 2015 byMarkus Winand Required Tooling for Key-Set Pagination Proper Row- Values Support. Don’t use OFFSET. Offer Infinite Scrolling. Don’t think in pages. Ask the right question.
  • 51.
  • 52.
    © 2015 byMarkus Winand About Markus Winand Tuning developers for high SQL performance Training & co (one-man show): winand.at Geeky blog: use-the-index-luke.com Author of: SQL Performance Explained