Tuesday, March 8, 2016

Premature Optimization

Context

This topic has bugged me for most of my career in software development.  In nearly every project I've been involved with, there's always someone on the team who wants to solve all the anticipated performance "problems" well before any such problem has been encountered.  The trip off into the weeds of analysis-paralysis usually starts with some wild, worst-case-scenario supposition that often derives from a fundamental misunderstanding of real-world system demands (actual load) or a fundamentally flawed mental calculation regarding real-world resource capacity (e.g. processing or network speed).

Manifesto

Bottom line: DON'T PREMATURELY OPTIMIZE!!!

In nearly every circumstance I can remember, it would have been WAY easier to just write the single-threaded, memory-hogging, un-cached, poorly organized, klunker of a piece of software, and THEN profile it to see where it really suffered in a real-world load scenario.  Attempting to anticipate which optimizable areas will present the real problems is a sure-fire way to waste 80% of the available development time figuring out how to solve 20% of the contribution to poor performance.  That old 80/20 rule can be turned in your favor so easily by waiting until you're 80% done with the functional bits and then spend 20% as much effort fixing the things that contribute 80% of the "slow."

Demonstration

If you didn't get the point by now, I'd be wasting my time to try to convince you.  Just try suppressing your urge to make that next program/module you write run in 0.1ms until you have enough information to understand that the rest of the system won't notice any difference until your stuff takes more than 15 seconds to complete.

AWS Lambda Java Deployment Maven Build

Summary


This post describes a "recipe" for building a deployment package for an Amazon AWS Lambda function authored in Java.  The current AWS Lambda documentation (links below in the "references" section) suggest using the "shade" plugin with Maven, which produces an "uber-jar", or using Gradle to produce a zip file containing classes and a "lib" subdirectory with dependency jars.  Neither of those approaches is ideal.  "Uber-jars" cause problems with any code that uses reflection, and using Gradle seems to assume you've completely set Maven aside as a build tool.  This "recipe" assumes you want to use Maven, but you'd still like the zip-containing-jars style deployment package instead of an "uber-jar."

Use Maven's Assembly Plugin


This should be at least a good 90%+ head start on configuring Maven to produce an AWS Lambda deployment package with the dependency libraries still in their original jars and your AWS Lambda function code included in package-directory / class-file format.

Create Assmembly Descriptor

Within the Maven project directory structure, create a file named:

        src/assembly/lambda_deployment_package_assembly.xml

...containing the following content...

<assembly xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
    <id>lambda_deployment_package_assembly</id>
    <formats>
        <format>zip</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <fileSets>
        <fileSet>
            <directory>${project.build.directory}/classes</directory>
            <outputDirectory>/</outputDirectory>
            <includes>
                <include>**/*.class</include>
            </includes>
        </fileSet>
        <fileSet>
            <outputDirectory>extras</outputDirectory>
            <includes>
                <include>readme.md</include>
            </includes>
        </fileSet>
    </fileSets>
    <dependencySets>
        <dependencySet>
            <outputDirectory>lib</outputDirectory>
            <useProjectArtifact>false</useProjectArtifact>
        </dependencySet>
    </dependencySets>
</assembly>

Add Plugin Configuration to pom.xml

Note: If this is a multi-module project, with each module producing its own AWS Lambda function deployment package (.zip), then this goes in the sub-module's pom.xml, not the parent.  If you have a lot of AWS Lambda function sub-modules, it might be worth trying to use pluginManagement in the parent pom, but that's outside the scope of this post.

pom.xml (excerpt)

<project>
    <dependencies>
    ...
    </dependencies>
    ...
    <plugins>
        <plugin>
            <!--  Note: Normally, this plugin would be run using
            "mvn compile assembly:single" but the execution
            clause below binds it to the normal 'package' lifecycle phase
            -->
            <artifactId>maven-assembly-plugin</artifactId>
            <configuration>
                <descriptors>
                    <descriptor>src/assembly/lambda_deployment_package_assembly.xml</descriptor>
                </descriptors>
            </configuration>
            <executions>
                <execution>
                    <id>lambda_deployment_package_execution</id>
                    <phase>package</phase>
                    <goals>
                        <goal>single</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
    ...
</project>

References


  • http://docs.aws.amazon.com/lambda/latest/dg/create-deployment-pkg-zip-java.html
  • http://docs.aws.amazon.com/lambda/latest/dg/java-create-jar-pkg-maven-no-ide.html