WSSCode Blog

Pathom Updates 12

March 12, 2022

Hello everyone! It’s been a while since we had a Pathom Update post, so let’s catch up on what’s been happening since the last one!

Pathom Viz

Pathom Viz got some love! Let’s see the updates.

Proportional resize panels

A long-standing issue for me was the resize handlers around all the app. In the past, they were based on absolute pixel sizes, which is terrible when we resize the app because some panels might just completely collapse.

Now that’s changed, and all the sizes are based on percentages, this way as you resize the full window, the panels will keep the proportional sizes.

Improvements to connect via URL

Pathom 3 URL Connect

Now when connecting via URL you can send custom headers. This will enable the usage passing an API token to authenticate your calls.

Pathom 3 query settings

Pathom 3 Knobs on Viz

I have added two new checkboxes when connecting to a Pathom 3 client. You can toggle if you want lenient mode active, or/and if you want to include or not the trace data.

Pathom 3 entity data on query history

Pathom 3 Entity data on history

A new feature for Pathom 3, now you can see in the query history the entity data used in that request.

Record Entity data on query history

The query history now remembers both the query and the entity data used, so you can come back to a past query in a click.

Parallel Processor

The parallel processor is here!

In Pathom 3, the parallel processor leverages the planner and have an implementation that’s much simpler than the Pathom 2 counterpart, resulting in an algorithm that requires fewer resources.

The parallel processor is an extension of the async processor. You can find details on how to use the parallel processor at https://pathom3.wsscode.com/docs/async/#parallel-process.

Benchmarks

It’s been a while since we have some performance comparisons, last time Pathom 3 was in very early stages, and lacking features makes it easier to go faster. Now that Pathom 3 is close to feature complete, how is it performing? Let’s find out!

note

I ran all the benchmarks in the JVM on a Macbook Pro 2019, 2.4 GHz 8-Core Intel Core i9, 64GB memory.

I used the criterium library for the measurements.

Single read

As I did last time, the first benchmark measures the time to do the minimal work to get some result of Pathom. It’s a query with a single attribute, which requires a single resolver call to realize.

[:x]
RunnerMeanVariance
Pathom 3 Serial Cached Plan0.016ms1.000x
Pathom 3 Serial0.018ms1.134x
Pathom 3 Async Cached Plan0.069ms4.456x
Pathom 3 Async0.062ms3.977x
Pathom 3 Parallel Cached Plan0.040ms2.543x
Pathom 3 Parallel0.045ms2.866x
Pathom 2 Serial0.040ms2.564x
Pathom 2 Async0.082ms5.284x
Pathom 2 Parallel0.125ms8.027x

Before moving on, let’s understand what the bars represent, from left to right:

  • p3-serial-cp: Pathom 3 serial parser, using a persistent plan cache. This means that the planning part of the operation is already pre-cached. It’s common for APIs to make the same queries over and over, this provides the opportunity to cache this step, and you may expect a high cache hit rate on this plan cache. They are based on the query and the available data.
  • p3-serial: Pathom 3 serial, without any pre-cache. Remember that even if there is no pre-cache, Pathom will make a request level plan cache anyway. When there is a collection in the process, the plan is cached and re-utilized between the collection items.
  • p3-async-cp Pathom 3 using async processor with cached plan
  • p3-async Pathom 3 using async processor
  • p3-parallel-cp Pathom 3 using parallel processor with cached plan
  • p3-parallel Pathom 3 using parallel processor
  • p2-serial: Pathom 2 using the serial parser and reader2.
  • p2-async: Pathom 2 using the async parser and async-reader2.
  • p2-parallel: Pathom 2 using the parallel parser and parallel-reader.

Complex read

This example demonstrates a case where a single attribute requires a complex chain of resolvers to get fulfilled.

[:h]
RunnerMeanVariance
Pathom 3 Serial Cached Plan0.023ms1.000x
Pathom 3 Serial0.041ms1.756x
Pathom 3 Async Cached Plan0.054ms2.322x
Pathom 3 Async0.079ms3.383x
Pathom 3 Parallel Cached Plan0.037ms1.592x
Pathom 3 Parallel0.072ms3.094x
Pathom 2 Serial0.593ms25.537x
Pathom 2 Async1.122ms48.301x
Pathom 2 Parallel1.331ms57.302x

In this example, we can see the cache plan was even more effective, which makes sense given this requires the generation of many nodes while previous was a plan with a single node.

Here is what the process of :h looks like in this example:

Complex Graph

tip

If you are not familiar with how Pathom 3 planning works and like to know more about the nodes generation check this documentation page.

Sequence of 1000 items

In this example, we will look at how long it takes for Pathom to process a collection with 1000 items in it, a simple resolver call on each.

[{:items-1000 [:x]}]
RunnerMeanVariance
Pathom 3 Serial Cached Plan26.191ms1.000x
Pathom 3 Serial27.098ms1.035x
Pathom 3 Async Cached Plan44.104ms1.684x
Pathom 3 Async46.486ms1.775x
Pathom 3 Parallel Cached Plan36.635ms1.399x
Pathom 3 Parallel36.540ms1.395x
Pathom 2 Serial104.952ms4.007x
Pathom 2 Async221.077ms8.441x
Pathom 2 Parallel71.848ms2.743x

In this, we can see that Pathom 3 gets about four times faster for raw sequence processing when compared to Pathom 2 serial.

Sequence of 1000 items in batch

Sample as the previous, but now with batched value processing.

[{:items-1000 [:x-batch]}]
RunnerMeanVariance
Pathom 3 Serial Cached Plan30.523ms1.024x
Pathom 3 Serial29.815ms1.000x
Pathom 3 Async Cached Plan46.741ms1.568x
Pathom 3 Async47.478ms1.592x
Pathom 3 Parallel Cached Plan54.079ms1.814x
Pathom 3 Parallel53.074ms1.780x
Pathom 2 Serial102.560ms3.440x
Pathom 2 Async221.441ms7.427x
Pathom 2 Parallel60.993ms2.046x

Same similar gains in batching.

Nesting 10 > 10 > 10

Testing nesting, 3 levels down, 10 items per level.

[{:items-10
  [:x
   {:items-10
    [:y
     {:items-10
      [:x]}]}]}]
RunnerMeanVariance
Pathom 3 Serial Cached Plan26.138ms1.000x
Pathom 3 Serial26.573ms1.017x
Pathom 3 Async Cached Plan53.316ms2.040x
Pathom 3 Async53.056ms2.030x
Pathom 3 Parallel Cached Plan41.959ms1.605x
Pathom 3 Parallel42.299ms1.618x
Pathom 2 Serial75.318ms2.882x
Pathom 2 Async166.487ms6.370x
Pathom 2 Parallel78.092ms2.988x

Nesting 100 > 10, batch head

Nesting with batching, the batch occurring at the top.

[{:items-100
  [:x-batch
   {:items-10
    [:y]}]}]
RunnerMeanVariance
Pathom 3 Serial Cached Plan26.738ms1.000x
Pathom 3 Serial27.626ms1.033x
Pathom 3 Async Cached Plan52.391ms1.959x
Pathom 3 Async54.656ms2.044x
Pathom 3 Parallel Cached Plan51.150ms1.913x
Pathom 3 Parallel53.442ms1.999x
Pathom 2 Serial78.397ms2.932x
Pathom 2 Async173.438ms6.487x
Pathom 2 Parallel68.285ms2.554x

Nesting 100 > 10, batch tail

Nesting with batching, the batch occurring at tail items.

[{:items-100
  [:y
   {:items-10
    [:x-batch]}]}]
RunnerMeanVariance
Pathom 3 Serial Cached Plan38.248ms1.039x
Pathom 3 Serial36.820ms1.000x
Pathom 3 Async Cached Plan77.091ms2.094x
Pathom 3 Async77.893ms2.116x
Pathom 3 Parallel Cached Plan75.088ms2.039x
Pathom 3 Parallel67.457ms1.832x
Pathom 2 Serial93.422ms2.537x
Pathom 2 Async225.680ms6.129x
Pathom 2 Parallel77.276ms2.099x

Sequence of 100 items, batch with 200ms delay

[{:items-100
  [(:x-batch {:delay 200})]}]
RunnerMeanVariance
Pathom 3 Serial Cached Plan210.946ms1.000x
Pathom 3 Serial212.334ms1.007x
Pathom 3 Async Cached Plan214.686ms1.018x
Pathom 3 Async216.780ms1.028x
Pathom 3 Parallel Cached Plan221.750ms1.051x
Pathom 3 Parallel218.851ms1.037x
Pathom 2 Serial224.137ms1.063x
Pathom 2 Async240.222ms1.139x
Pathom 2 Parallel217.378ms1.030x

This is a good example of how when some resolvers are costly, the noticeable overhead gets reduced.

Nesting 10 > 10, batch with 20ms delay

[{:items-10
  [{:items-10
    [(:x-batch {:delay 20})]}]}]
RunnerMeanVariance
Pathom 3 Serial Cached Plan33.437ms1.003x
Pathom 3 Serial33.327ms1.000x
Pathom 3 Async Cached Plan35.565ms1.067x
Pathom 3 Async35.890ms1.077x
Pathom 3 Parallel Cached Plan41.128ms1.234x
Pathom 3 Parallel40.723ms1.222x
Pathom 2 Serial40.241ms1.207x
Pathom 2 Async55.795ms1.674x
Pathom 2 Parallel40.733ms1.222x

Complex read structure

This example is closer to what a complex user request looks like. Where parts are independent and the parallel features can give a real boost in the speed:

[{:>/profile-info
  [:customer/name
   :customer/birthday
   :customer/genre
   :account/status]}
 {:>/purchases
  [{:customer/purchases
    [:purchase/vendor
     :purchase/amount
     :purchase/category]}]}
 {:>/account-info
  [:account/status
   :account/status-history
   :account/onboarding-date
   :account/balance
   :account/balance-future]}
 {:>/chargebacks
  [{:customer/chargebacks
    [:chargeback/date
     :chargeback/status]}]}
 {:>/credit-cards
  [{:account/credit-cards
    [:credit-card/masked-number
     :credit-card/status]}]}
 {:>/boletos
  [{:customer/boletos
    [:boleto/number
     :boleto/paid?]}]}
 {:>/mgm
  [{:customer/referrals
    [:customer/name]}]}
 {:>/phones
  [{:customer/phones
    [:phone/id
     :phone/number]}]}]
RunnerMeanVariance
Pathom 3 Serial Cached Plan819.869ms6.774x
Pathom 3 Serial821.332ms6.786x
Pathom 3 Async Cached Plan789.007ms6.519x
Pathom 3 Async791.661ms6.541x
Pathom 3 Parallel Cached Plan121.029ms1.000x
Pathom 3 Parallel125.183ms1.034x
Pathom 2 Serial807.025ms6.668x
Pathom 2 Async820.699ms6.781x
Pathom 2 Parallel164.130ms1.356x

As we can see, the parallel processors shine here, Pathom 3 being the fastest. Its also worth to note that the Pathom 3 implementation uses significantly fewer resources than the Pathom 2 due to the new parallel implementation.

I think it’s interesting to see the trace of the parallel process vs the serial in this case:

Serial Trace

Parallel Trace

Here you can find the full sources for these benchmarks: https://gist.github.com/wilkerlucio/f2a52d032101a5c436045e621e331b4c

Pathom 3 GraphQL

Pathom 3 GraphQL got an official release!

If you want to integrate some GraphQL source into your Pathom system, check it out at https://github.com/wilkerlucio/pathom3-graphql.


Follow closer

If you like to know in more details about my projects check my open Roam database where you can see development details almost daily.

Support my work

I'm currently an independent developer and I spent quite a lot of my personal time doing open-source work. If my work is valuable for you or your company, please consider supporting my work though Patreon, this way you can help me have more available time to keep doing this work. Thanks!

Current supporters

And here I like to give a thanks to my current supporters:

Albrecht Schmidt
Alister Lee
Austin Finlinson
Daemian Mack
Jochen Bedersdorfer
Kendall Buchanan
Mark Wardle
Michael Glaesemann
Oleg, Iar, Anton
West