Skip to content

Replace Netty based RFC 1123 date encoding (#3527)#3550

Merged
987Nabil merged 3 commits intozio:mainfrom
987Nabil:custom-date-encoding
Jun 27, 2025
Merged

Replace Netty based RFC 1123 date encoding (#3527)#3550
987Nabil merged 3 commits intozio:mainfrom
987Nabil:custom-date-encoding

Conversation

@987Nabil
Copy link
Copy Markdown
Contributor

@987Nabil 987Nabil commented Jun 13, 2025

fixes #3527
/claim #3527

@netlify
Copy link
Copy Markdown

netlify Bot commented Jun 13, 2025

Deploy Preview for zio-http ready!

Name Link
🔨 Latest commit cac56dd
🔍 Latest deploy log https://app.netlify.com/projects/zio-http/deploys/685c1b980c93cd000808ad5d
😎 Deploy Preview https://deploy-preview-3550--zio-http.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@plokhotnyuk
Copy link
Copy Markdown
Contributor

Here is an efficient implementation of parsing/formatting for different timestamp formats including RFC 1123. Most performance tricks were ported from jsoniter-scala routines for JSON parsing and serialization of different data time values.

Java's DateTimeFormatter is ~10x times slower and affects scalability by eating memory bandwidth greatly. Even when using caching for formatting it introduces much bigger tail latency during refreshing of formatted values.


def decodeDate(date: String): Option[ZonedDateTime] = {
try {
if (date.head.isUpper && date.charAt(3) == ',' && date.charAt(4) == ' ')
Copy link
Copy Markdown
Member

@guizmaii guizmaii Jun 19, 2025

Choose a reason for hiding this comment

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

date.head throws if date is empty.
Should we maybe prefer to do:

if (date.isEmpty) None 
else if (date.charAt(0).isUppe && ...) 
  ...

?
Probably a good change for performances

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I checked instead, that length is 28 or 29 since only these are valid.
Theoretically 27 is valid (year before 1k) But I think it is okay to ignore

private def decodeRFC1123(date: String): Option[ZonedDateTime] = {
{
// TODO consider trie-hard approach
val c0 = date.head
Copy link
Copy Markdown
Member

@guizmaii guizmaii Jun 19, 2025

Choose a reason for hiding this comment

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

Prefer date.charAt(0)? It avoids an if (string.isEmpty) check present in the .head method

Related: https://github.com/zio/zio-http/pull/3550/files#r2155802128

@987Nabil 987Nabil force-pushed the custom-date-encoding branch from 459b391 to d5f8ea8 Compare June 20, 2025 17:31
@987Nabil
Copy link
Copy Markdown
Contributor Author

@guizmaii Implemented some benchmarks

Before your suggested changes

Benchmark                                      Mode  Cnt        Score         Error  Units
DateEncodingBenchmark.nettyDecoding           thrpt    3   439288.041 ± 1092690.136  ops/s
DateEncodingBenchmark.nettyDecodingInvalid    thrpt    3   427059.216 ±  895231.682  ops/s
DateEncodingBenchmark.nettyEncoding           thrpt    3   635390.868 ± 1403521.676  ops/s
DateEncodingBenchmark.zioHttpDecoding         thrpt    3  2317595.133 ± 2830576.722  ops/s
DateEncodingBenchmark.zioHttpDecodingInvalid  thrpt    3  2443173.737 ±  219913.014  ops/s
DateEncodingBenchmark.zioHttpEncoding         thrpt    3  1095266.738 ±  378289.638  ops/s

After

Benchmark                                      Mode  Cnt         Score         Error  Units
DateEncodingBenchmark.nettyDecoding           thrpt    3    471698.844 ±  401495.065  ops/s
DateEncodingBenchmark.nettyDecodingInvalid    thrpt    3    433314.955 ±  208199.739  ops/s
DateEncodingBenchmark.nettyEncoding           thrpt    3    596804.828 ± 1649578.829  ops/s
DateEncodingBenchmark.zioHttpDecoding         thrpt    3   2327353.872 ± 2900047.041  ops/s
DateEncodingBenchmark.zioHttpDecodingInvalid  thrpt    3  17922976.451 ± 3374509.274  ops/s
DateEncodingBenchmark.zioHttpEncoding         thrpt    3   1054091.409 ± 1728102.469  ops/s

The invalid case has explicitly strings with invalid length.

@987Nabil 987Nabil force-pushed the custom-date-encoding branch 2 times, most recently from a7cfa72 to 7196299 Compare June 20, 2025 22:43
@nau
Copy link
Copy Markdown

nau commented Jun 22, 2025

Cool job! Although why not make these changes in Netty code for everyone to benefit?

Comment thread zio-http/shared/src/main/scala/zio/http/internal/DateEncoding.scala Outdated

def decodeDate(date: String): Option[ZonedDateTime] = {
try {
if (date.length != 29 && date.length != 28) return None
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@plokhotnyuk Is there any cost of calling date.length twice? Or the compiler and/or JIT will optimise this? 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

String length is just a call to an internal field of the string impl. I don't think there is a real cost for this call.

@guizmaii
Copy link
Copy Markdown
Member

@nau

Although why not make these changes in Netty code for everyone to benefit?

I can be wrong but I think the idea is probably to have this code in pure Scala so that it compiles to JS and Native and reduces the dependency on Netty

@987Nabil
Copy link
Copy Markdown
Contributor Author

@nau @guizmaii There are two reasons.

  1. We want to be independent of Netty. Not only for JS/Native, but also to be able to offer different backends in the JVM.
  2. Nettys formatter supports not only RFC1123 dates. We can optimize here by failing for any other date then RFC1123 dates

@987Nabil 987Nabil force-pushed the custom-date-encoding branch from 7196299 to cac56dd Compare June 25, 2025 15:53
@987Nabil 987Nabil merged commit 782f763 into zio:main Jun 27, 2025
43 checks passed
encodeRFC1123(date)

def decodeDate(date: String): Option[ZonedDateTime] = {
if (date.length != 29 && date.length != 28) return None
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@987Nabil
Shouldn't it be || instead of &&? 🤔

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.

Replace DateEncoding with own impl

4 participants