zio-config icon indicating copy to clipboard operation
zio-config copied to clipboard

Very slow performance with large number of env vars represented as nested config

Open notxcain opened this issue 4 years ago • 26 comments

When you run your app on Kubernetes there are a lot of env vars defined. At least two for each service. So in my case there are 800 of env vars like LONG_SERVICE_NAME_HOST and ANOTHER_LONG_SERVICE_NAME_PORT.

And it seems that config loading is stuck forever in PropertyTree#mergeAll.

notxcain avatar Oct 13 '20 15:10 notxcain

@notxcain thanks for reporting the issue. I will try to reproduce this and take a look at this issue.

afsalthaj avatar Oct 13 '20 20:10 afsalthaj

+1

amitksingh1490 avatar Oct 28 '20 06:10 amitksingh1490

For our use-case (100kb of env variables) we get an out of memory error —

java.lang.OutOfMemoryError: Java heap space
	at scala.collection.immutable.BitmapIndexedMapNode.copyAndSetNode(HashMap.scala:937)
	at scala.collection.immutable.BitmapIndexedMapNode.updated(HashMap.scala:730)
	at scala.collection.immutable.HashMap.updated(HashMap.scala:152)
	at scala.collection.immutable.HashMap.updated(HashMap.scala:39)
	at scala.collection.immutable.MapOps.$plus(Map.scala:133)
	at scala.collection.immutable.MapOps.$plus$(Map.scala:133)
	at scala.collection.immutable.AbstractMap.$plus(Map.scala:641)
	at zio.config.PropertyTree.$anonfun$merge$6(PropertyTree.scala:135)
	at zio.config.PropertyTree$$Lambda$547/0x0000000100330040.apply(Unknown Source)
	at scala.collection.immutable.List.map(List.scala:246)
	at zio.config.PropertyTree.$anonfun$merge$1(PropertyTree.scala:135)
	at zio.config.PropertyTree$$Lambda$543/0x000000010032d040.apply(Unknown Source)
	at scala.collection.IterableOnceOps.foldLeft(IterableOnce.scala:638)
	at scala.collection.IterableOnceOps.foldLeft$(IterableOnce.scala:634)
	at scala.collection.AbstractIterable.foldLeft(Iterable.scala:920)
	at zio.config.PropertyTree.merge(PropertyTree.scala:128)
	at zio.config.PropertyTree.merge$(PropertyTree.scala:123)
	at zio.config.PropertyTree$Record.merge(PropertyTree.scala:223)
	at zio.config.PropertyTree$.$anonfun$mergeAll$2(PropertyTree.scala:277)
	at zio.config.PropertyTree$$$Lambda$541/0x000000010032b840.apply(Unknown Source)
	at scala.collection.immutable.List.flatMap(List.scala:293)
	at zio.config.PropertyTree$.$anonfun$mergeAll$1(PropertyTree.scala:277)
	at zio.config.PropertyTree$$$Lambda$539/0x000000010032a840.apply(Unknown Source)
	at scala.collection.LinearSeqOps.foldLeft(LinearSeq.scala:168)
	at scala.collection.LinearSeqOps.foldLeft$(LinearSeq.scala:164)
	at scala.collection.immutable.List.foldLeft(List.scala:79)
	at zio.config.PropertyTree$.mergeAll(PropertyTree.scala:276)
	at zio.config.PropertyTree$.unflatten(PropertyTree.scala:271)
	at zio.config.ConfigSourceStringModule$ConfigSource$.fromMapInternal(ConfigSourceModule.scala:439)
	at zio.config.ConfigSourceStringModule$ConfigSource$.fromMap(ConfigSourceModule.scala:226)
	at zio.config.ConfigSourceStringModule$ConfigSource$.$anonfun$fromSystemEnv$3(ConfigSourceModule.scala:386)
	at zio.config.ConfigSourceStringModule$ConfigSource$$$Lambda$124/0x00000001001b5840.apply(Unknown Source)
java.lang.OutOfMemoryError: Java heap space: failed reallocation of scalar replaced objects

tusharmath avatar Oct 28 '20 06:10 tusharmath

@notxcain Could you also give the details of the source (Example: is it key -> value property file or HOCON) ? What's the target data structure (Example: case class, Map?)

For example, this works:


  val map =
    (0 to 5000).toList.map(r => s"LONG_SERVICE_NAME_HOST_${r}" -> "value").toMap

  val getHost = string("LONG_SERVICE_NAME_HOST_5000")

  println(read(getHost from ConfigSource.fromMap(map)))

afsalthaj avatar Oct 28 '20 08:10 afsalthaj

@afsalthaj I've sanitized our env vars https://gist.github.com/notxcain/c21f24e2d1f8e8e692512acdb496c599.

notxcain avatar Oct 28 '20 09:10 notxcain

Sure. Could you also share the code that tries to retrieve the config?

On Wed, 28 Oct 2020 at 8:59 PM, Denis Mikhaylov [email protected] wrote:

@afsalthaj https://github.com/afsalthaj I've sanitized our env vars https://gist.github.com/notxcain/c21f24e2d1f8e8e692512acdb496c599.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/zio/zio-config/issues/418#issuecomment-717825572, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABY2QJIQSQQJMCBVEMQWXK3SM7TPXANCNFSM4SPD6MIA .

afsalthaj avatar Oct 28 '20 10:10 afsalthaj

@afsalthaj just use this map in your example.

notxcain avatar Oct 28 '20 10:10 notxcain

@notxcain Worked image

afsalthaj avatar Oct 28 '20 12:10 afsalthaj

Damn, I must've oversanitized something. It fails on unsanitized env vars. Let me recheck.

notxcain avatar Oct 28 '20 12:10 notxcain

@notxcain Sure, thanks. Meanwhile I am looking at @tusharmath's example that has a much more complicated retrieval by passing delimiter to 100KB key value pair.

afsalthaj avatar Oct 28 '20 12:10 afsalthaj

@notxcain See if you are also passing delimiters. Example: val mapSource = ConfigSource.fromMap(source, keyDelimiter = Some('_')) Then we all are on the same page.

afsalthaj avatar Oct 28 '20 12:10 afsalthaj

Ah, of course! Sorry I didn't mention that.

notxcain avatar Oct 28 '20 13:10 notxcain

@afsalthaj so now you see it too?

notxcain avatar Oct 28 '20 13:10 notxcain

Yes, only when passing the delimiters. That's how I reproduced the issue. Fixing mergeAll function now

afsalthaj avatar Oct 28 '20 13:10 afsalthaj

@afsalthaj how is it going? Did you find the solution?

notxcain avatar Nov 03 '20 13:11 notxcain

@afsalthaj sorry to bother you, but do you have an update?

notxcain avatar Nov 09 '20 12:11 notxcain

@notxcain could I get back to you in a while. For the time being if you filter the values first in sys.env and then pass it as a reduced map to zio-config for the time being, it will fix the issue. The issue is when converting a 800 plus flattened values as nested structure using delimiters.

afsalthaj avatar Nov 09 '20 13:11 afsalthaj

@notxcain give me a bit more time, and I hope il fix it soon. Currently busy with Api docs and zio-columnar

afsalthaj avatar Nov 09 '20 13:11 afsalthaj

As a workaround, we used system properties populated from env vars.

notxcain avatar Nov 09 '20 13:11 notxcain

Have you thought about lazy source design? So that you don't have to convert all the env vars, and just access on demand.

notxcain avatar Nov 09 '20 13:11 notxcain

@notxcain Sorry to get back late. I was really busy with some of the other changes.

The memory issue comes into picture when there are large number of environment variables in the source with nested behaviour, that is, when passing explicit passing for delimiters.

The quick fix for you is not to pass delimiters if there are too many delimited key-value pairs in flattened sources. We will be looking at a proper solution than jumping on to a quick hack :)

Thanks for the patience.

afsalthaj avatar Dec 03 '20 23:12 afsalthaj

A mutable implementation of mergeAll would fix this, as would, potentially, making source more lazy. I think I'd prefer the former as if you have thousands of env variables inside a property tree, laziness won't help.

Anyone want to volunteer for a mutable version of mergeAll? A good exercise in high-performance functional Scala.

jdegoes avatar Dec 13 '20 15:12 jdegoes

@jdegoes That would be me ✋

tusharmath avatar Dec 14 '20 04:12 tusharmath

@tusharmath Awesome! Let me know if you would like to pair on it or need additional clarification! 🙏

jdegoes avatar Dec 14 '20 18:12 jdegoes

Hi guys, I see that there is a standing PR. That problem is a bit of a blocker for me. Any idea about when this can be deployed?

remiguittaut avatar Aug 31 '21 07:08 remiguittaut

There is a slight redesign going on at the configsource side. We have an eye on this.

afsalthaj avatar Aug 31 '21 15:08 afsalthaj