Hi, I recently moved my Kobweb site into my genera...
# kobweb
s
Hi, I recently moved my Kobweb site into my general Kotlin Multiplatform project in order to share logic with my Android and iOS client. This has also enabled my Kobweb site to use Koin, Ktor and Firebase (GitLive). But it seems that this has caused a large increase in memory usage. I am currently trying to target this:
Copy code
org.gradle.jvmargs=-Xmx512m
kotlin.daemon.jvmargs=-Xmx768m
But if I run the application in a docker container limited to 512mb I immediately run into an OutOfMemoryException. Increasing the container memory limit to 956mb(!) ‘resolves’ the issue. But I can also see from the container stats that once running the application idles around 300mb. I don’t have much experience with memory usage optimisation, but I guess I need to be more thoughtful about when I load and initialize my frameworks. (Is this a lazy process when running a JAR?) I am looking for any pointers or experience regarding memory optimization. Here is the full stack trace:
Copy code
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.base/java.util.Arrays.copyOf(Arrays.java:3537)
	at java.base/java.io.ByteArrayOutputStream.toByteArray(ByteArrayOutputStream.java:185)
	at kotlin.io.ByteStreamsKt.readBytes(IOStreams.kt:137)
[...]	com.varabyte.kobweb.server.ApplicationKt.main(Application.kt)
Researching a bit in how to heap analysis, I am now running VisualVM and the server.jar locally, though I am unable to see the memory go over the 300mb I am running this command:
Copy code
java -Xmx512m -Dkobweb.server.environment=PROD -Dkobweb.site.layout=FULLSTACK -Dio.ktor.development=false -jar .kobweb/server/server.jar
Since I cannot reproduce the issue locally, I wonder if there might be an issue with my docker file? It is mostly based on this https://bitspittle.dev/blog/2023/cloud-deploy#add-a-dockerfile I downgraded to JDK 17 as my Multiplatform target JVM_17
Copy code
#-----------------------------------------------------------------------------
# Variables are shared across multiple stages (they need to be explicitly
# opted # into each stage by being declaring there too, but their values need
# only be # specified once).
ARG KOBWEB_APP_ROOT="app/site"

FROM openjdk:17-jdk-slim AS java

#-----------------------------------------------------------------------------
# Create an intermediate stage which builds and exports our site. In the
# final stage, we'll only extract what we need from this stage, saving a lot
# of space.
FROM java AS export

ENV KOBWEB_CLI_VERSION=0.9.16
ARG KOBWEB_APP_ROOT

ENV NODE_MAJOR=20

# Copy the project code to an arbitrary subdir so we can install stuff in the
# Docker container root without worrying about clobbering project files.
COPY . /project

# Update and install required OS packages to continue
# Note: Node install instructions from: <https://github.com/nodesource/distributions#installation-instructions>
# Note: Playwright is a system for running browsers, and here we use it to
# install Chromium.
RUN apt-get update \
    && apt-get install -y ca-certificates curl gnupg unzip wget \
    && mkdir -p /etc/apt/keyrings \
    && curl -fsSL <https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key> | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \
    && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] <https://deb.nodesource.com/node_$NODE_MAJOR.x> nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \
    && apt-get update \
    && apt-get install -y nodejs \
    && npm init -y \
    && npx playwright install --with-deps chromium

# Fetch the latest version of the Kobweb CLI
RUN wget <https://github.com/varabyte/kobweb-cli/releases/download/v${KOBWEB_CLI_VERSION}/kobweb-${KOBWEB_CLI_VERSION}.zip> \
    && unzip kobweb-${KOBWEB_CLI_VERSION}.zip \
    && rm kobweb-${KOBWEB_CLI_VERSION}.zip

ENV PATH="/kobweb-${KOBWEB_CLI_VERSION}/bin:${PATH}"

ARG BUILD_ENV="PROD"
ENV BUILD_ENV=$BUILD_ENV

WORKDIR /project/${KOBWEB_APP_ROOT}

# Decrease Gradle memory usage to avoid OOM situations in tight environments
# (many free Cloud tiers only give you 512M of RAM). The following amount
# should be more than enough to build and export our site.
RUN mkdir ~/.gradle && \
    echo "org.gradle.jvmargs=-Xmx512m" >> ~/.gradle/gradle.properties

RUN kobweb export --notty

#-----------------------------------------------------------------------------
# Create the final stage, which contains just enough bits to run the Kobweb
# server.
FROM java AS run

ARG KOBWEB_APP_ROOT

COPY --from=export /project/${KOBWEB_APP_ROOT}/.kobweb .kobweb

ENTRYPOINT [".kobweb/server/start.sh"]
d
Did your site change outside of getting moved into a new project?
(Also, no sweat, but in the future, it's probably better to create a new thread before dumping a huge code block, or using the text snippet option as mentioned in the usage guidelines )
👍 1
Note that the Docker container needs to have enough memory to run your site, of course, but also build it, which is more expensive.
Did you see this comment in the Dockerfile?
Copy code
# Decrease Gradle memory usage to avoid OOM situations in tight environments
# (many free Cloud tiers only give you 512M of RAM). The following amount
# should be more than enough to build and export our site.
RUN mkdir ~/.gradle && \
    echo "org.gradle.jvmargs=-Xmx512m" >> ~/.gradle/gradle.properties
so my guess is you are running out of memory building your site but not running it.
But yeah, so far I'm not sure. Kobweb shouldn't care if you're inside a larger multiplatform project or not, unless you are depending on other modules now which themselves are huge?
s
Thank you for the replies and sorry about the huge code block ! The site did also change when moving into the new project, integrating some of the shared features. But the other modules are not that large, from my perspective. It sounds plausable that building the site might what causes the oom. I am paying for the lowest non-free tier on render, and that only has 512 mb, so as long as my gradle config match that I expect I should be good, unless I actually need to use more. Though the step up to the next plan costs 3 times as much. So I would really like to avoid that. Looking into the render logs it looks like building the docker image completes correctly, but when running the image it immediate runs in to an OOM:
Copy code
Nov 19 10:05:13 AM
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Nov 19 10:05:10 AM
2024-11-19 09:05:10.550 [main] INFO  ktor.application - API jar found and will be loaded: ".kobweb/site/system/pixelpark.jar"
Nov 19 10:05:10 AM
2024-11-19 09:05:10.052 [main] INFO  ktor.application - Autoreload is disabled because the development mode is off.
I have been able to reproduce the issue locally when I run the docker image with a memory limit like this:
docker run -m 512m kobweb_site
I just tried creating a dockerfile that simply copies the .kobweb folder that I build locally from clear build cache with the same command as the original dockerfile and runs it as usual. In this case I also get OOM when running:
docker run -m 512m kobweb_site_local
BUT then I tried to modify the start.sh java args by adding the additional
-Xmx512m
arg:
Copy code
args="-Xmx512m -Dkobweb.server.environment=PROD -Dkobweb.site.layout=FULLSTACK -Dio.ktor.development=false -jar .kobweb/server/server.jar"
If I then build the image again (That just takes the local copy) and runs the image, I no longer get an OOM exception.
Is there a way to configure the java args that are generated for
start.sh
?
Or maybe I can adjust the final docker stage to limit the java environment
If I change my original docker file final stage to the following I no longer OOM:
Copy code
# Create the final stage, which contains just enough bits to run the Kobweb
# server.
FROM java AS run

ARG KOBWEB_APP_ROOT

COPY --from=export /project/${KOBWEB_APP_ROOT}/.kobweb .kobweb

# ENTRYPOINT [".kobweb/server/start.sh"]
CMD ["java", "-Xmx512m", "-Dkobweb.server.environment=PROD", "-Dkobweb.site.layout=FULLSTACK", "-Dio.ktor.development=false", "-jar", ".kobweb/server/server.jar"]
Though it does not seem like a good idea to ignore the start script (You might add other stuff to that in future releases.
I ended up doing this instead:
Copy code
# Prepend -Xmx512m to the args line in start.sh
RUN sed -i 's|^args="\(.*\)"|args="-Xmx512m \1"|' .kobweb/server/start.sh
This will allow me to keep using the start script, though modified to use the extra arg
Just deployed this to render and it successfully deployed 🙌
d
That's very interesting. Thank you!
I'll look into letting users configure the start.sh script with arguments
or maybe I should add a memory limit support to the conf.yaml file. I will think about it.
s
Aha, yeah that seems like a better way to solve it, compared to what I did
d
I haven't tried this but maybe this will work?
Copy code
ENV JAVA_TOOL_OPTIONS="-Xmx512m"
ENTRYPOINT [".kobweb/server/start.sh"]
s
Yes, that worked!
Copy code
Picked up JAVA_TOOL_OPTIONS: -Xmx512m
2024-11-20 17:48:15.642 [main] INFO ktor.application - Autoreload is disabled because the development mode is off.
2024-11-20 17:48:15.656 [main] INFO ktor.application - API jar found and will be loaded: ".kobweb/site/system/pixelpark.jar"
2024-11-20 17:48:16.715 [main] INFO ktor.application - Loaded and initialized server API jar in 11ms.
2024-11-20 17:48:16.760 [main] INFO ktor.application - Application started in 1.148 seconds.
2024-11-20 17:48:16.951 [DefaultDispatcher-worker-1] INFO ktor.application - Responding at <http://0.0.0.0:8080>
d
Awesome! Thanks for confirming
Just FYI, I've updated my blog site's Dockerfile thanks to this conversation https://bitspittle.dev/blog/2023/cloud-deploy#add-a-dockerfile
🙌 1
Full disclosure: I'm not 100% sure I'll leave the ENV line in there -- maybe the better recommendation is to tell people to add that line to their Render config instead, I'm not sure. But since I'm restricting memory for the export process a bit above (to 300M), it doesn't seem unreasonable to me at the moment to include that bit there as well.