Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add QueryParams which deprecates HttpParameters #2307

Merged
merged 49 commits into from
Dec 20, 2019

Conversation

trustin
Copy link
Member

@trustin trustin commented Dec 10, 2019

Motivation:

Modifications:

  • Fork our HTTP headers API into:
    • QueryParams
    • QueryParamGetters
    • QueryParamsBase
    • QueryParamsBuilder
  • Extract the common logic and API between HTTP API and query params API into:
    • StringMultimap
    • StringMultimapGetters
  • Add support for QueryParams to annotated services.
  • Update documentation.
  • Deprecate HttpParameters.
  • Replace the usage of Netty QueryString{Encoder,Decoder} with:
    • QueryParamGetter.toQueryString() and appendQueryString()
    • QueryParams.fromQueryString()
  • Add microbenchmarks for QueryString{Encoder,Decoder}.

Result:

Motivation:

- line#1567

Modifications:

- Fork our HTTP headers implementation into:
  - `QueryParams`
  - `QueryParamGetters`
  - `QueryParamsBase`
  - `QueryParamsBuilder`
  - .. and their implementations.
  - Note that it is intentional that I didn't introduce any common
    classes between `HttpHeaders` and `QueryParams`. They are just
    similar to each other accidentally and thus share nothing
    semantically close in common in my opinion. Please let me know if
    you do not like this approach - we should discuss.
- Add support for `QueryParams` to annotated services.
- Deprecate `HttpParameters`.

Result:

- Closes line#1567

To-dos:

- Add encoding/decoding methods like we did for `Cookie`.
- Update `annotated-services.rst`
@trustin
Copy link
Member Author

trustin commented Dec 10, 2019

@anuraaga @minwoox @ikhoon PTAL. Will remove the draft tag once I finish all to-dos, but it'd be nice if you could take a look and tell me this is the right direction. :-)

@anuraaga
Copy link
Collaborator

I agree with the idea of not sharing when they are semantically different. But it's just so so much code... I'm not a fan but I see why we need to.

I guess we do want to redefine the API since indeed they are separate concepts but can we at least share code for the storage implementation? I would be ok with just one extra object allocation to have storage as a separate class, or it might be ok to use a base class.

@trustin
Copy link
Member Author

trustin commented Dec 11, 2019

@anuraaga Will try. 😅

@codecov
Copy link

codecov bot commented Dec 11, 2019

Codecov Report

Merging #2307 into master will increase coverage by 0.08%.
The diff coverage is 69.92%.

Impacted file tree graph

@@             Coverage Diff             @@
##             master   #2307      +/-   ##
===========================================
+ Coverage     73.62%   73.7%   +0.08%     
- Complexity    10182   10314     +132     
===========================================
  Files           885     894       +9     
  Lines         38950   39346     +396     
  Branches       4818    4891      +73     
===========================================
+ Hits          28677   29001     +324     
- Misses         7816    7864      +48     
- Partials       2457    2481      +24
Impacted Files Coverage Δ Complexity Δ
.../java/com/linecorp/armeria/common/HttpRequest.java 73.73% <ø> (ø) 26 <0> (ø) ⬇️
...om/linecorp/armeria/server/docs/TypeSignature.java 87.14% <ø> (ø) 30 <0> (ø) ⬇️
...om/linecorp/armeria/common/logging/RequestLog.java 23.52% <ø> (ø) 8 <0> (ø) ⬇️
...corp/armeria/common/DefaultHttpHeadersBuilder.java 100% <ø> (ø) 8 <0> (ø) ⬇️
...rmeria/common/logging/LoggingDecoratorBuilder.java 77.77% <ø> (ø) 39 <0> (ø) ⬇️
.../main/java/com/linecorp/armeria/common/Cookie.java 28.57% <ø> (ø) 22 <0> (ø) ⬇️
.../armeria/common/DefaultResponseHeadersBuilder.java 95% <ø> (ø) 9 <0> (ø) ⬇️
.../java/com/linecorp/armeria/common/HttpHeaders.java 46.87% <ø> (ø) 8 <0> (ø) ⬇️
...corp/armeria/server/jetty/JettyServiceBuilder.java 30% <ø> (ø) 4 <0> (ø) ⬇️
...ava/com/linecorp/armeria/server/file/HttpFile.java 58.33% <ø> (ø) 5 <0> (ø) ⬇️
... and 59 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update aedd226...4ae61e4. Read the comment docs.

@trustin
Copy link
Member Author

trustin commented Dec 11, 2019

  • Added StringMultimap and StringMultimapBuilder to extract common logic.
  • Tried to introduce a common interface type for multimap builders to ensure all builder interfaces have a common set of builder methods, but it seemed to overcomplicate things, so gave up. Added some test cases to prevent a future mistake instead. See StringMultimapDerivedApiConsistencyTest.

@trustin trustin marked this pull request as ready for review December 12, 2019 07:32
@trustin trustin requested a review from anuraaga December 12, 2019 07:32
@trustin
Copy link
Member Author

trustin commented Dec 12, 2019

Now ready for review. PTAL @anuraaga @minwoox @ikhoon

Copy link
Collaborator

@anuraaga anuraaga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did a small pass, it mostly looks good. Thanks for the huge work!

Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Amazing work! 👍👍

@trustin
Copy link
Member Author

trustin commented Dec 19, 2019

The result from fd1c4e5

Benchmark                                    Mode  Cnt       Score       Error  Units
QueryStringDecoderBenchmark.armeriaAscii    thrpt   50  380672.412 ±  8104.627  ops/s
QueryStringDecoderBenchmark.armeriaLong     thrpt   50   17063.315 ±   617.047  ops/s
QueryStringDecoderBenchmark.armeriaMixed    thrpt   50  119307.118 ±  2963.182  ops/s
QueryStringDecoderBenchmark.armeriaUnicode  thrpt   50   85732.344 ±  2011.455  ops/s
QueryStringDecoderBenchmark.nettyAscii      thrpt   50  343379.090 ±  9079.795  ops/s
QueryStringDecoderBenchmark.nettyLong       thrpt   50    9065.173 ±   249.774  ops/s
QueryStringDecoderBenchmark.nettyMixed      thrpt   50   61184.914 ±   354.602  ops/s
QueryStringDecoderBenchmark.nettyUnicode    thrpt   50   49656.054 ±   558.771  ops/s
QueryStringEncoderBenchmark.armeriaAscii    thrpt   50  761883.482 ± 16652.428  ops/s
QueryStringEncoderBenchmark.armeriaLong     thrpt   50   21469.565 ±   500.053  ops/s
QueryStringEncoderBenchmark.armeriaMixed    thrpt   50  210244.395 ±  4789.462  ops/s
QueryStringEncoderBenchmark.armeriaUnicode  thrpt   50  204346.461 ±   938.928  ops/s
QueryStringEncoderBenchmark.guavaAscii      thrpt   50  774993.351 ±  2737.659  ops/s
QueryStringEncoderBenchmark.guavaLong       thrpt   50   22641.740 ±   150.873  ops/s
QueryStringEncoderBenchmark.guavaMixed      thrpt   50  195272.956 ±  1802.613  ops/s
QueryStringEncoderBenchmark.guavaUnicode    thrpt   50  138458.690 ±  1285.093  ops/s
QueryStringEncoderBenchmark.jdkAscii        thrpt   50  264616.790 ±   620.668  ops/s
QueryStringEncoderBenchmark.jdkLong         thrpt   50    7872.475 ±    44.410  ops/s
QueryStringEncoderBenchmark.jdkMixed        thrpt   50   63819.362 ±   303.451  ops/s
QueryStringEncoderBenchmark.jdkUnicode      thrpt   50   51462.477 ±   179.308  ops/s
QueryStringEncoderBenchmark.nettyAscii      thrpt   50  443927.583 ±   783.943  ops/s
QueryStringEncoderBenchmark.nettyLong       thrpt   50   14472.656 ±   163.951  ops/s
QueryStringEncoderBenchmark.nettyMixed      thrpt   50  124317.750 ±  4115.201  ops/s
QueryStringEncoderBenchmark.nettyUnicode    thrpt   50  102374.791 ±   137.208  ops/s

@trustin
Copy link
Member Author

trustin commented Dec 19, 2019

541796b helped a lot. 8383ef9 helped a little bit.

@trustin
Copy link
Member Author

trustin commented Dec 19, 2019

@anuraaga @minwoox Ready for another round.

@trustin
Copy link
Member Author

trustin commented Dec 19, 2019

Slightly better numbers after 8383ef9. Now we are on par with or better than Guava except for one case (ASCII).

Benchmark                                    Mode  Cnt       Score      Error  Units
QueryStringEncoderBenchmark.armeriaAscii    thrpt   50  751692.461 ± 2123.210  ops/s
QueryStringEncoderBenchmark.armeriaLong     thrpt   50   21553.386 ±  839.109  ops/s
QueryStringEncoderBenchmark.armeriaMixed    thrpt   50  221688.950 ± 1553.724  ops/s
QueryStringEncoderBenchmark.armeriaUnicode  thrpt   50  211791.166 ± 1017.269  ops/s

Copy link
Collaborator

@anuraaga anuraaga left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for starting a huge tangent 😆 we probably should have done the optimization in a separate PR. Anyways, the only thing blocking approval last "real" review was the leading question mark issue but now LGTM!


@SuppressWarnings("checkstyle:AvoidEscapedUnicodeCharacters")
private static final char UNKNOWN_CHAR = '\uFFFD';
private static final byte[] OCTETS_TO_HEX = new byte['f' + 1];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I may stick with int[] since the difference in size is so minor, but it could give the JVM a better time of finding some optimization.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure. I'd prefer byte[] because it's more compact and thus better utilization of CPU cache.

@franz1981
Copy link

Beware stack allocations of arrays ie https://mobile.twitter.com/forked_franz/status/1193481376122777601

@trustin
Copy link
Member Author

trustin commented Dec 19, 2019

Result from: 1cb66cb

Benchmark                                    Mode  Cnt       Score       Error  Units
QueryStringEncoderBenchmark.armeriaAscii    thrpt    5  796213.603 ± 17510.214  ops/s
QueryStringEncoderBenchmark.armeriaLong     thrpt    5   22438.634 ±   300.509  ops/s
QueryStringEncoderBenchmark.armeriaMixed    thrpt    5  214785.444 ±  5345.319  ops/s
QueryStringEncoderBenchmark.armeriaUnicode  thrpt    5  211279.841 ±  3465.634  ops/s
QueryStringEncoderBenchmark.guavaAscii      thrpt    5  769711.170 ± 12697.817  ops/s
QueryStringEncoderBenchmark.guavaLong       thrpt    5   22709.233 ±   377.663  ops/s
QueryStringEncoderBenchmark.guavaMixed      thrpt    5  197058.689 ±  8895.232  ops/s
QueryStringEncoderBenchmark.guavaUnicode    thrpt    5  139209.275 ±  1154.131  ops/s

@trustin
Copy link
Member Author

trustin commented Dec 19, 2019

@franz1981 wrote:

Beware stack allocations of arrays ie mobile.twitter.com/forked_franz/status/1193481376122777601

Thanks for the information. Does it mean JVM will not stack-allocate any array whose length is derived from user input? e.g.

static void func(int length) {
    final int values[] = new int[length];
    // ... `values` does not escape ...
}

@franz1981
Copy link

franz1981 commented Dec 19, 2019

@trustin I need to re-read the heuristic of the EA, but looking at the simple JMH bench I've sent, yes: you are both limited on the array size by a JVM params and it seems OpenJDK (!= GraalVM) is more conservative, so I suggest to run your benchmarks with -prof gc or just use a profiler (async-profiler) to check allocations during your bench...
I wouldn't rely on the JVM to stack allocate things given the latest results I've got on this...my 2 cents.

Copy link
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! learned a lot. 😄

@trustin
Copy link
Member Author

trustin commented Dec 20, 2019

Thanks a lot, @franz1981

Complete result from 1cb66cb with 10 forks:

Benchmark                                    Mode  Cnt       Score      Error  Units
QueryStringDecoderBenchmark.armeriaAscii    thrpt   50  378741.654 ± 6659.789  ops/s
QueryStringDecoderBenchmark.armeriaLong     thrpt   50   17363.007 ±  711.308  ops/s
QueryStringDecoderBenchmark.armeriaMixed    thrpt   50  120362.631 ± 2517.298  ops/s
QueryStringDecoderBenchmark.armeriaUnicode  thrpt   50   86819.473 ± 1580.938  ops/s
QueryStringDecoderBenchmark.nettyAscii      thrpt   50  352954.120 ± 1303.213  ops/s
QueryStringDecoderBenchmark.nettyLong       thrpt   50    9216.124 ±  285.652  ops/s
QueryStringDecoderBenchmark.nettyMixed      thrpt   50   62038.399 ±  453.026  ops/s
QueryStringDecoderBenchmark.nettyUnicode    thrpt   50   49561.678 ±  659.775  ops/s
QueryStringEncoderBenchmark.armeriaAscii    thrpt   50  802149.965 ± 4023.009  ops/s
QueryStringEncoderBenchmark.armeriaLong     thrpt   50   21525.055 ±  801.401  ops/s
QueryStringEncoderBenchmark.armeriaMixed    thrpt   50  224702.833 ± 1475.840  ops/s
QueryStringEncoderBenchmark.armeriaUnicode  thrpt   50  210494.132 ± 8571.742  ops/s
QueryStringEncoderBenchmark.guavaAscii      thrpt   50  774172.127 ± 4041.075  ops/s
QueryStringEncoderBenchmark.guavaLong       thrpt   50   22669.871 ±  184.624  ops/s
QueryStringEncoderBenchmark.guavaMixed      thrpt   50  199361.612 ± 1562.677  ops/s
QueryStringEncoderBenchmark.guavaUnicode    thrpt   50  139650.766 ±  991.154  ops/s
QueryStringEncoderBenchmark.jdkAscii        thrpt   50  265860.618 ±  511.369  ops/s
QueryStringEncoderBenchmark.jdkLong         thrpt   50    7932.388 ±   59.079  ops/s
QueryStringEncoderBenchmark.jdkMixed        thrpt   50   64110.415 ±  299.827  ops/s
QueryStringEncoderBenchmark.jdkUnicode      thrpt   50   51915.861 ±  145.560  ops/s
QueryStringEncoderBenchmark.nettyAscii      thrpt   50  438574.293 ± 8949.848  ops/s
QueryStringEncoderBenchmark.nettyLong       thrpt   50   14510.677 ±  159.799  ops/s
QueryStringEncoderBenchmark.nettyMixed      thrpt   50  126900.454 ± 1168.554  ops/s
QueryStringEncoderBenchmark.nettyUnicode    thrpt   50  101262.449 ± 1587.243  ops/s

@trustin trustin merged commit df376e9 into line:master Dec 20, 2019
@trustin trustin deleted the immutable_http_params branch December 20, 2019 05:40
@trustin
Copy link
Member Author

trustin commented Dec 20, 2019

Thanks everyone for reviews and advices. 👍

fmguerreiro pushed a commit to fmguerreiro/armeria that referenced this pull request Sep 19, 2020
Motivation:

- line#1567

Modifications:

- Fork our HTTP headers API into:
  - `QueryParams`
  - `QueryParamGetters`
  - `QueryParamsBase`
  - `QueryParamsBuilder`
- Extract the common logic and API between HTTP API and query params API into:
  - `StringMultimap`
  - `StringMultimapGetters`
- Add support for `QueryParams` to annotated services.
- Update documentation.
- Deprecate `HttpParameters`.
- Replace the usage of Netty `QueryString{Encoder,Decoder}` with:
  - `QueryParamGetter.toQueryString()` and `appendQueryString()`
  - `QueryParams.fromQueryString()`

Result:

- Closes line#1567
- Nicer and faster way to decode/encode a query string.

```
Benchmark                                    Mode  Cnt       Score      Error  Units
QueryStringDecoderBenchmark.armeriaAscii    thrpt   50  378741.654 ± 6659.789  ops/s
QueryStringDecoderBenchmark.armeriaLong     thrpt   50   17363.007 ±  711.308  ops/s
QueryStringDecoderBenchmark.armeriaMixed    thrpt   50  120362.631 ± 2517.298  ops/s
QueryStringDecoderBenchmark.armeriaUnicode  thrpt   50   86819.473 ± 1580.938  ops/s
QueryStringDecoderBenchmark.nettyAscii      thrpt   50  352954.120 ± 1303.213  ops/s
QueryStringDecoderBenchmark.nettyLong       thrpt   50    9216.124 ±  285.652  ops/s
QueryStringDecoderBenchmark.nettyMixed      thrpt   50   62038.399 ±  453.026  ops/s
QueryStringDecoderBenchmark.nettyUnicode    thrpt   50   49561.678 ±  659.775  ops/s
QueryStringEncoderBenchmark.armeriaAscii    thrpt   50  802149.965 ± 4023.009  ops/s
QueryStringEncoderBenchmark.armeriaLong     thrpt   50   21525.055 ±  801.401  ops/s
QueryStringEncoderBenchmark.armeriaMixed    thrpt   50  224702.833 ± 1475.840  ops/s
QueryStringEncoderBenchmark.armeriaUnicode  thrpt   50  210494.132 ± 8571.742  ops/s
QueryStringEncoderBenchmark.guavaAscii      thrpt   50  774172.127 ± 4041.075  ops/s
QueryStringEncoderBenchmark.guavaLong       thrpt   50   22669.871 ±  184.624  ops/s
QueryStringEncoderBenchmark.guavaMixed      thrpt   50  199361.612 ± 1562.677  ops/s
QueryStringEncoderBenchmark.guavaUnicode    thrpt   50  139650.766 ±  991.154  ops/s
QueryStringEncoderBenchmark.jdkAscii        thrpt   50  265860.618 ±  511.369  ops/s
QueryStringEncoderBenchmark.jdkLong         thrpt   50    7932.388 ±   59.079  ops/s
QueryStringEncoderBenchmark.jdkMixed        thrpt   50   64110.415 ±  299.827  ops/s
QueryStringEncoderBenchmark.jdkUnicode      thrpt   50   51915.861 ±  145.560  ops/s
QueryStringEncoderBenchmark.nettyAscii      thrpt   50  438574.293 ± 8949.848  ops/s
QueryStringEncoderBenchmark.nettyLong       thrpt   50   14510.677 ±  159.799  ops/s
QueryStringEncoderBenchmark.nettyMixed      thrpt   50  126900.454 ± 1168.554  ops/s
QueryStringEncoderBenchmark.nettyUnicode    thrpt   50  101262.449 ± 1587.243  ops/s
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Immutable HttpHeaders and Cookies
5 participants