$30 off During Our Annual Pro Sale. View Details »

Fixing a performance issue with the mustache gem

Fixing a performance issue with the mustache gem

@alicebartlett and I gave this talk at LRUG, on Monday 12th October 2015.

Dominic Baggott

October 12, 2015
Tweet

Other Decks in Programming

Transcript

  1. fixing a performance issue with
    the mustache gem
    (a bit less boring than it sounds)

    View Slide

  2. @alicebartlett
    &
    @evilstreak

    View Slide

  3. We used to work at
    GDS, on gov.uk

    View Slide

  4. In this talk:
    - Adding a feature
    - Finding a bug
    - working around the bug
    - fixing the bug

    View Slide

  5. GOV.uk is the best place
    to find government
    services and
    information

    View Slide

  6. These are Manuals
    They are multi-
    page long form
    documents

    View Slide

  7. New feature:
    search within a manual

    View Slide

  8. This is search within
    a manual
    A list of results
    from a Manual with a
    nested list of
    results from the
    rest of GOV.UK

    View Slide

  9. From this:

    View Slide

  10. To this:
    From this:

    View Slide

  11. View Slide

  12. Search pages use mustache
    (specifically: the ruby implementation of mustache via the
    mustache gem)

    View Slide

  13. From this:

    View Slide

  14. To this:
    From this:

    View Slide

  15. To this:
    then from this:

    View Slide

  16. This is very
    uninteresting.

    View Slide

  17. Using partials in this way is in the docs:
    from: https://mustache.github.io/mustache.5.html#Partials

    View Slide

  18. full PR is here:
    https://github.com/alphagov/frontend/pull/784

    View Slide

  19. Time to deploy!

    View Slide

  20. This should have been
    straightforward

    View Slide

  21. View Slide

  22. -

    View Slide

  23. -

    View Slide

  24. We deploy to
    staging

    View Slide

  25. We proceed to
    production

    View Slide

  26. Slowly, shit hits the
    fan.

    View Slide

  27. -

    View Slide

  28. We rollback and
    investigate on staging

    View Slide

  29. Breaking things isn’t bad
    if you have a good plan to
    recover

    View Slide

  30. Profiling

    View Slide

  31. We found out the problem
    was this CPU spike

    View Slide

  32. # Profile the code
    RubyProf.start
    ...
    [code to profile]
    ...
    result = RubyProf.stop
    # Print a flat profile to text
    printer = RubyProf::FlatPrinter.new(result)
    printer.print(STDOUT)
    Measuring code with ruby-prof is easy

    View Slide

  33. Profile of our code
    %self total self wait child calls name
    5.41 0.271 0.086 0.000 0.185 675 Mustache::Parser#scan_tags
    3.90 0.062 0.062 0.000 0.000 36 BasicObject#instance_eval
    3.34 1.031 0.053 0.000 0.978 1 ActionView::CompiledTemplates#_app
    2.98 0.047 0.047 0.000 0.000 6 #[]
    2.52 0.042 0.040 0.001 0.001 74
    2.36 0.040 0.037 0.000 0.002 1095 Mustache::Parser#regexp
    2.08 0.051 0.033 0.000 0.018 46 #load
    1.99 0.032 0.032 0.000 0.000 6 Kernel#require
    1.98 0.032 0.032 0.000 0.000 2181 String#to_s
    1.67 0.506 0.027 0.000 0.480 1007 *Array#each
    1.55 0.050 0.025 0.000 0.025 972 Psych::ScalarScanner#tokenize
    1.48 0.024 0.024 0.000 0.000 168 #exist?
    1.35 0.068 0.021 0.000 0.046 663 Mustache::Parser#position
    1.31 0.066 0.021 0.000 0.045 675 Mustache::Parser#scan_text

    View Slide

  34. Profile of our code
    %self total self wait child calls name
    5.41 0.271 0.086 0.000 0.185 675 Mustache::Parser#scan_tags
    3.90 0.062 0.062 0.000 0.000 36 BasicObject#instance_eval
    3.34 1.031 0.053 0.000 0.978 1 ActionView::CompiledTemplates#_app
    2.98 0.047 0.047 0.000 0.000 6 #[]
    2.52 0.042 0.040 0.001 0.001 74
    2.36 0.040 0.037 0.000 0.002 1095 Mustache::Parser#regexp
    2.08 0.051 0.033 0.000 0.018 46 #load
    1.99 0.032 0.032 0.000 0.000 6 Kernel#require
    1.98 0.032 0.032 0.000 0.000 2181 String#to_s
    1.67 0.506 0.027 0.000 0.480 1007 *Array#each
    1.55 0.050 0.025 0.000 0.025 972 Psych::ScalarScanner#tokenize
    1.48 0.024 0.024 0.000 0.000 168 #exist?
    1.35 0.068 0.021 0.000 0.046 663 Mustache::Parser#position
    1.31 0.066 0.021 0.000 0.045 675 Mustache::Parser#scan_text
     Method name 
     Time 
     Calls 

    View Slide

  35. Profile of our code
    %self total self wait child calls name
    5.41 0.271 0.086 0.000 0.185 675 Mustache::Parser#scan_tags
    3.90 0.062 0.062 0.000 0.000 36 BasicObject#instance_eval
    3.34 1.031 0.053 0.000 0.978 1 ActionView::CompiledTemplates#_app
    2.98 0.047 0.047 0.000 0.000 6 #[]
    2.52 0.042 0.040 0.001 0.001 74
    2.36 0.040 0.037 0.000 0.002 1095 Mustache::Parser#regexp
    2.08 0.051 0.033 0.000 0.018 46 #load
    1.99 0.032 0.032 0.000 0.000 6 Kernel#require
    1.98 0.032 0.032 0.000 0.000 2181 String#to_s
    1.67 0.506 0.027 0.000 0.480 1007 *Array#each
    1.55 0.050 0.025 0.000 0.025 972 Psych::ScalarScanner#tokenize
    1.48 0.024 0.024 0.000 0.000 168 #exist?
    1.35 0.068 0.021 0.000 0.046 663 Mustache::Parser#position
    1.31 0.066 0.021 0.000 0.045 675 Mustache::Parser#scan_text

    View Slide

  36. Before and after
    self calls name self calls name
    0.051 2 BasicObject#instance_eva 0.086 675 Mustache::Parser#scan_ta
    0.042 6 #[] 0.062 36 BasicObject#instance_eva
    0.014 110 Mustache::Parser#scan_ta 0.053 1 ActionView::CompiledTemp
    0.014 337 *Array#each 0.047 6 #[]
    0.004 145 Sprockets::Asset#depende 0.040 74 0.004 149 *Array#map 0.037 1095 Mustache::Parser#regexp
    0.003 110 Mustache::Parser#scan_te 0.033 46 #load
    0.003 109 Mustache::Parser#positio 0.032 6 Kernel#require
    0.003 110 Mustache::Parser#scan_un 0.032 2181 String#to_s
    0.002 2 #binread 0.027 1007 *Array#each
    0.002 186 #exist? 0.025 972 Psych::ScalarScanner#tok
    0.002 774 Kernel#is_a? 0.024 168 #exist?
    0.002 186 Hike::Trail#stat 0.021 663 Mustache::Parser#positio
    0.002 232 String#split 0.021 675 Mustache::Parser#scan_te

    View Slide

  37. compiling templates is slow
    and partials are compiled
    before each use

    View Slide

  38. Recursion

    View Slide

  39. Our results page has
    a list within a list

    View Slide

  40. This is broken
    This is OK

    View Slide

  41. - Iteration
    - Repetition
    - Recursion

    View Slide

  42. - Iteration
    - Repetition
    - Recursion

    View Slide

  43. Repetition

    View Slide

  44. Repetition

    View Slide

  45. This is fast but
    ghastly

    View Slide

  46. - Iteration
    - Repetition ??
    - Recursion

    View Slide

  47. Recursion is a bit of a
    dick move

    View Slide

  48. Recursion: Use responsibly

    View Slide

  49. _results.mustache:
    {{#results}}
    {{#is_multiple_results}}
    {{>_results}}
    {{/is_multiple_results}}
    {{^is_multiple_results}}
    result code goes here
    {{/is_multiple_results}}
    {{/results}}

    View Slide

  50. _results.mustache:
    {{#results}}
    {{#is_multiple_results}}
    {{>_results}} /*Call self*/
    {{/is_multiple_results}}
    {{^is_multiple_results}}
    result code goes here
    {{/is_multiple_results}}
    {{/results}}

    View Slide

  51. The problem with this
    is it’s non-obvious

    View Slide

  52. - Iteration
    - Repetition ??
    - Recursion ??

    View Slide

  53. View Slide

  54. Reader, we chose the
    recursion.

    View Slide

  55. Fixing the gem

    View Slide

  56. Fixing the gem was the
    right thing to do

    View Slide

  57. Fixing the gem prevents
    trouble in the future

    View Slide

  58. Looking for prior art

    View Slide

  59. The fix

    View Slide

  60. We added a benchmark
    instead of a test

    View Slide

  61. Writing the pull request
    https://github.com/mustache/mustache/pull/205

    View Slide

  62. Explain the problem

    View Slide

  63. Explain the change

    View Slide

  64. Be proactive about
    objections

    View Slide

  65. Keep nudging
    for progress

    View Slide

  66. Over half our effort
    went on getting the
    change accepted

    View Slide

  67. OK thanks bye

    View Slide

  68. Links

    View Slide

  69. Questions?

    View Slide