Who is responsible for using resources efficiently?
One overarching principle: Minimize resource usage
When looking at the core philosophies defined by Eric Raymond for Unix, there is one principle that should be revised given our global, society challenge of limiting resource usage, carbon emissions and overall moving to sustainable consumption & production across all sectors.
Machine time, or as we consider them - digital resource primitives (compute, memory, storage, network) - are not infinite, as the term cloud computing might suggest. They are finite, and ties to physical consumption of natural resources - since the end of Moore’s Law - in an almost linear relationship. If an application consumes more digital resource primitives, it consumes more hardware resources and though that energy and natural resources.
In simpler terms: Every CPU cycle, every GB of memory, GB of storage and network has an impact on the environment.
Hence the overarching principle for every software developer, product manager, designer or architect should be to minimize resource use of an application.
Recommendations
Our recommendations are building on the existing studies referenced below. However we divide them into three groups:
Functional recommendations, such as the ability to turn off functionalities, for roles/stakeholders such as user interface designers, user experience teams, product management, business analysts, etc.
Architecture recommendations, such as designing a modular technical architecture, for software architects, technical management, etc.
Development recommendations, such as avoiding blocks on future resources, for software developers, site reliability engineers, and operational engineering teams.
With this separation we want to ensure that the recommendations are relevant for the different roles who are involved in the making of a Digital Product. If a person occupies multiple roles (e.g. in a small startup) where product management and architecture might be combined, you can freely combine them.
Further, we need to segment the recommendations for different types of software applications. The categories have been defined in previous studies. They are numbered and referenced in the headline of each recommendation:
Local or client-side application (DE: lokale Anwendung)
Client-side application with remote data storage (DE: Anwendung mit entfernter Datenhaltung)
Client-side application with remote processing (DE: Anwendung mit entfernter Verarbeitung)
Server-side application (DE: Serverdienst)
Lastly we have ordered the recommendations by importance.
Functional Recommendations
Define requirements to minimize the hardware requirements (1, 2, 3, 4)
Defining a clear requirement to minimize the amount of hardware resources used by the applications has many benefits: it enables hardware to be used for a longer time, it is often times also increasing performance & the responsiveness of the application, and it allows the application to run on many more devices.
Many times the user is not using a state-of-the-art machine like the ones the development and product teams are using, but rather older, much less powerful machines. By designing for minimum hardware utilization, the majority of these users benefits while having also an overall reduced environmental impact.
From a product management & design perspective, the question to the development teams should be: How will this increase our hardware resource usage? Or: How many hardware resources will this feature use?
Instead of doing performance optimization later, define minimal resource usage as a requirement for each functionality from the start.
Design the application so that the user has control and visibility over the resource-consumption from their usage (1, 2, 3, 4)
It should be visible to the user which function (e.g. performing an export of an image) triggers how much resource consumption, both locally and on any involved server-side (remote) systems. The user should hence be enabled to chose not to perform or use a function due to its potentially excessive resource usage.
Ability for the user to turn off not-needed or unused functions of the software (1, 2, 3, 4)
It should be possible for a user to turn off functionalities of the software that are not needed. These ‘feature toggles’ should be connected to the underlying code & functionality, as this is an existing technique in software engineering (see Feature Flags byPete Hodgson). This means that when a user disables a feature, it should result in less code being executed in total, and thus should translate in a reduction in resource usage.
Support delay tolerance and slow connectivity (2)
When remote data sources are used, a slow or delayed connectivity should be supported by design. This not only leads to a better user experience but it enables the use of the application in low- or limited-bandwidth settings as well as reduces the network capacity use. This must be considered both in the user interface (to not block the entire functionality of the application from a delayed data connection) as well as in the technical architecture (e.g. pre-loading critical data when a connection is available, local caching, etc.)
Support an offline version of the application (2, 3)
As very common in mobile applications, any software application that works on a client (mobile or desktop) should support basic functionality when there is no connectivity. The application’s enhanced featured (e.g. that are resource intensive and therefore require remote processing or that depend on accessing data that can not be stored offline, e.g. from real-time data sources) can be disabled, but functionality that is technically possible to be provided offline should be present. This increases usability, leads to reduced network usage through local caching and less obsolescence, e.g. if the software vendor does not support the remote-services anymore, the application continues to provide basic functionality.
Ability to completely remove the application (1, 2, 3, 4)
It should be possible to remove the application in its entirety from a client (e.g. a desktop computer) with residual data removed (e.g. caches, log files, user data). In the case of remote storage or processing - any associated data with the user (or users in a multi-tenant environment) should be completely wiped to avoid stranded data which uses storage resources.
Turn off features that are not supported on a older hardware (1, 2, 3, 4)
Instead of making the entire application unavailable on older operating systems and client environments, rather opt for disabling features, e.g. using feature toggles. The application then may have less up-to-date functionality, but continues to perform the functionality the user has originally installed the application for on the client.
The other way to look at this is to not stop supporting a device (e.g. laptop or phone model), but rather unlock features only for capable hardware; this also expand the market potential of the application and helps expand the lifetime of hardware in general.
A well-known design principle is responsive design, e.g. responding to the screen size of the user when delivering a user interface. The same principle can be applied to the available hardware resources and functionalities. The application can simply offer features that respond to what is available on the client and hide the ones that can not be run in the client environment.
Delivery of the software only via internet (1, 2, 3, 4)
Modern software applications should not be shipped on physical media (CD, DVD, or any other form of physical storage) but rather be transferred through networks/internet.
Consider removing outdated user data or suggest to the user to remove data (1, 2, 3, 4)
Many applications deal with large amounts of data which are never being accessed, because the user has stopped using the product (but not deleted the account), or because the data is long obsolete. No company benefits from storing the unused information of its users. Therefore implement functionalities that help users outdated data, give indicators and suggestion where data can be removed, and make it simple to execute a recommendation.
Where possible also delete, archive or delete data that is obsolete and does not require user intervention or permission. Further, archive information that is infrequently accessed and integrate a ‘recovery from archive’ delay-interface into the user interactions. Data storage is not unlimited and free, it has a large environmental impact and should not be wasted.
Consider monitoring feature creep (1, 2, 3, 4)
Many software applications suffer growing more and more features, of which each bloats the software more, which leads to more hardware resources being used over time, even though there might be no new benefit to the user. Especially feature creep without feature toggles, e.g. letting users opt-in to new features if they need them, rather than continuously expanding functionalities, creates enormous pain for users who constantly have to adapt and leads to ever increasing resource-hunger of applications.
Consider monitoring your feature development and prioritization process either monitor it (e.g. performing some kind of cost-benefit analysis on each feature before developing it) or having an equal, feature-removal process to counterbalance the constant adding of new features.
Architecture Recommendations
Micro-services that represent application functionalities & core features (4)
The movement towards micro-services has been well underway in the software architecture ecosystem. The benefits are wide, to name a few: Independent scaling of components, separation of concerns and better collaboration between large development teams.
A micro-service architecture can have a big impact In terms of resource-usage especially in conjunction with a functional requirement/design that enables the user to turn off/on functionalities. If the micro-service architecture breaks up core services (such as storage, simple utility functions and other support services) and functionalities, when users all disable a functionality, the respective services can be turned off without impacting the overall application.
Further, using container-based orchestration, this scaling up and down can be directly reflected in the underlying infrastructure and lead to a measurable reduction in hardware resource usage.
Consider web-based user-interfaces that are operating-system independent (1, 2, 3)
Browser-based or web-based user interfaces have become more common, especially as they are operating-system independent. However, they often do not have access to the same the resource optimization opportunities of native applications that can directly interact with the operating system. The resource optimization of a web-based application is mostly managed by the browser environment.
However, for most digital products consider a lightweight, web-based interface first, reducing the hardware resource required on the client-computer to a minimum (e.g. must be enough to run a browser). This does not apply for CLI or command-line based tools (hence we refer to digital products and not applications in this case).
Before building native client-applications perform a proper analysis on performance & resource usage (1, 2, 3)
When building a native client application, consider the actual improvement in performance against a web-based client, especially considering maintainability and interoperability. Also consider how the potential additional hardware resources on the client may limit the types of devices that can be used to run the application.
Consider decoupling frontend and backend
Most modern applications have already embraced breaking up applications between the user interface (frontend) and the data storage & processing layer (backend). This design pattern in conjunction with separate micro-services for each API endpoint, can lead to a lot of resource savings, as APIs can be scaled independently based on the amount of requests that a single endpoint receives from the frontend application.
Further, it enables single features to be turned off in the frontend, and equally be shut off on the backend, without affecting the rest of the application.
Lastly the frontend is only running when someone is using the application and can be delivered statically (see static delivery).
Consider delivering a static frontend to and to shift most of processing and storage to server-side micro-services (1, 2, 3)
When web-based frontends are statically rendered at build time, the result is a fixed bundle of HTML, CSS and JavaScript, all static files that can be rendered within the browser (client-side) without a roundtrip to a server which needs to re-render the same page for each user.
Static frontends, further have the advantage that they can be served via content delivery networks - efficient caching systems that can deliver millions of static frontend applications with mostly storage & network resources. This delivery mechanism is much more compute efficient - for each client the compilation of the page only happens once, and is then delivered to each client where it’s rendered on the client-side.
When building static web-based frontends, the paradigm of shifting heavy computing and data storage to the server-side is more or less enforced, hence removing potential high hardware resource requirements.
Consider operating micro-services as function-like services that are switched off when not used (4)
Functions are the the next computing paradigm that is gaining traction - breaking down each functional unit of code into a micro-application that is executed only when someone is ‘calling’ it. In principle this is very resource efficient, e.g. an API which is never called, is never loaded into memory or allocated computing time.
You should consider if you can turn very basic, shared services, such as sending an email, processing/compressing an image, preparing a data export, into functions that are only running when they are triggered.
Consider moving as many operations as possible into asynchronous background processes that can be delayed or moved (4)
This is a common practice in the software development world, but it’s worth mentioning that all tasks that can be postponed in time or that can be shifted to a different location should be designed as a background task.
Background task should use a queue system that has a scheduler that can be optimized or extended to include considerations for the availability of renewable energy, shifting to a different data center or simply delaying the task.
You should also consider if you can move the queue and background processing to low-grade machines, or in Cloud environments to use spot instances for these tasks.
Support incremental, over the air updates that do not replace the entire application on each update-cycle (1, 2, 3)
When delivering updates to client-side applications, deliver them incrementally, e.g. do not require to replace all of the program code every time an update is delivered.
Use a full program code update only for ‘reset’ or self-repair functionalities.
Support public APIs on all main functionalities for the programmatic use & as well as data export (2, 4)
Making the data within the application technically accessible, is a key feature to enable composability between applications as well as the exporting of data in a technical format (e.g. to migrate into another application).
These public APIs should be well documented, and accessible to the user of the application.
Support data interoperability or export into common formats (2, 4)
Either in conjunction with a Public API or as a standalone functionality, support the ability to export all of the user-data in a standardized and machine-readable format, e.g. a ZIP file of JSON documents, YAML or XML. Enable the exporting of all the information the user has inputted to the system.
Consider measuring the total resource use as part of your integration tests to provide insights for product management and development on areas of improvement (1, 2, 3, 4)
When running your integration tests, e.g. testing that the functional requirements are met, consider also recording the total hardware resource usage of each functionality and reporting these metrics as part of your continuous integration process, or ticketing system, so that the organization that defines the functional requirements is aware of the resource costs of the functionality.
It should also help to inform your development teams to consider refactoring or code optimization and should overall also help improve application performance (less resource usage most likely will also convert into a performance gain).
The SoftAWERE tooling can support you to do those measurements.
Development Recommendations
Remove unnecessary libraries and favor libraries that provide required only functionality needed (1, 2, 3, 4)
Integrate the removal of unused libraries into your practice and workflows as it can reduce the size of the overall application and lead to an overall decrease in resource-usage.
Further consider integrating libraries that deliver a limited functionality, that meets current requirements rather than ‘super libraries’ that deliver functionality that may be useful in the future but are not needed right now. All of these bloat the size of the codebase and can (depending on the compiler, interpreter and programming language) also lead to increased runtime resource usage.
Minimize the hardware requirements for any client-side application (1, 2, 3)
Consider making hardware requirements as low as possible to support legacy hardware equipment as well. Today’s software applications (especially client-side) are often driving constant, unnecessary hardware requirements, simply because a new iteration of an application requires more resources. This has to avoided and maximizing the lifetime of the hardware should be paramount as it significantly contributes to reducing the environmental burden created by IT equipment and applications.
Furthermore, low hardware requirements on the client-side also lead to a better user experience and overall performance and expands the potential user-base of any application.
Lower hardware requirements can also be accomplished by delivering updates of applications with new functionality default ‘switched off’ and only loading the application code of a new feature when the user has explicitly switched it ‘on’. This way a user can receive updates, without those updating driving the obsolescence of the equipment they are using.
Report the resource-usage of a single operation and make it visible to the client-side (1, 2, 3, 4).
In order to raise the awareness of users towards the resource usage of their actions within an application, or of the functionalities they are using as well as their overall usage behavior, it is paramount to make the underlying resource-usage transparent.
Each action of a user should have a clearly understandable label that informs about the energy usage, carbon emissions from the energy & remote infrastructure that was used to perform processing or store data.
When building a server-side application, e.g. an API, consider embedding the resource-usage information into the HTTP header or any other transport header, so it can be displayed in the user interface.
The SDIA’s digital environmental footprint framework and open source tools can help make this information visible in an application.
Turn off staging & test environments (1, 2, 3, 4)
Unless your development is spread across many unrelated time-zones (e.g. West Coast of the United States and Barcelona, Spain), consider turning off development environments outside of office hours or at least at night, e.g. 22:00 - 04:00. Even the maybe minor resource usage of these systems creates waste if they are not utilized.
If possible, run these environments in containerized or at least virtualized, shared resource environments so that if these test & staging environment receive limited load or stress, the underlying hypervisor & scheduler can re-allocate the unused resources.
Do not over-provision these systems either, but rather constrain them to ensure that resource-usage is always minimized in new feature developments.
Don’t run builds on each minor code-change (1, 2, 3, 4)
Most development environments are configured to trigger a new build or test-run on every file change. Even if a simple README file is changed, a new build maybe triggered. Make sure to configure your systems so that new builds and test-runs are only triggered when a real code change has happened.
Consider switching continuous integration & delivery to a semi-manual process. Keep in mind that each run is using energy and resources, which are neither infinite nor do they have no environmental impact. The opposite is the case and resource usage should be minimized, also during the development process.
Shift processing and storage to server-side if it helps reduce client-side obsoleteness (1, 2, 3, 4)
When introducing new functionalities that would lead to a drastic increase of client-side resource usage or a change in the minimum hardware requirements, consider migrating the processing or storage into a remote-environment.
Driving the obsoleteness of client-side hardware should be avoided whenever possible, as hardware lifetime extensions can lead to significant environmental benefits and also ensure the application can be run on a much wider range of hardware.
Ensure restart-ability, and up- and down-scaling (4)
To ensure maximum optimization potential, e.g. in virtualized or containerized infrastructure environments, ensure that the application can be started/stopped without any intervention and performs necessary health-checks automatically, repairing itself (e.g. clearing caches and triggering another restart itself).
Provide proper signaling when the application is corrupted, a restart would interrupt a task or anything else so that schedulers and optimizers can migrate & optimize the application.
Try to avoid linking the application to any requirements on the physical infrastructure and allow it to be fully virtualized.
When it comes to developing web-based frontend applications, many software developers & vendors have opted for a mobile-first approach, meaning that the user interface will first be developed for the smallest, least capable screen.
The same principle can be applied to developing client-side applications as well as server-side applications, working ‘bottom-up’, e.g. assuming the smallest possible available resource or a old device as the baseline for development. As an example, what would an application look like that has to work Intel Celeron with 2 GB memory and a 80 GB HDD?
This principle can be found a lot across the linux community, and has enabled very high-performing and resource efficient operating systems & tools, which are powering many server-environments today, because of their ‘low overhead’. It’s time to bring these principles back into application development as well.
Do not block resources that are not being used (1, 2, 3, 4)
It has become common practice for IT departments and developers to request hardware resources ‘that might be needed when it scales’ or that are provisioned because the actual load/stress on the system is still unknown. In some cases this is warranted, however, the overall architecture should then quickly be resized to match the actual load & stress of the application.
The same goes for the application itself, e.g. reserving all of available memory even though it’s not needed removes any possibility of another application or process to use the memory while its not in use. Most hypervisors & resource provisioning systems are more than capable to reallocate the required resources back to the application when they are needed. Blocking resources that could be used (even just temporarily) by another application or system, should be avoided.
Zero work should equal near-zero resource-use (except background processes) (1, 2, 3, 4)
When an application does not perform any action, it should consume as little hardware resources as possible. In an idling state (except background processes that might need to run to clean up, garbage collection, etc.), the hardware resources should be freed up again (e.g. memory) and the usage of CPU resources should be close to none.
Avoid code & software bloat (1, 2, 3, 4)
Resource usage should always be considered with each iteration of the software application. Often, when new features are introduced or ‘speed’, developer productivity is valued over efficiency (see Wikipedia excerpt below), application resource usage can grow quickly.
Hence each cycle of development (new feature, refactoring) should re-evaluate the total resource usage of the application and each respective feature. The tools developed by the SoftAWERE project can support this transparency during the development process.
Actual (measurable) bloat can occur due to de-emphasising algorithmic efficiency
in favour of other concerns like developer productivity, or possibly through the introduction of new layers of abstraction like a virtual machine or other scripting engine for the purposes of convenience when developer constraints are reduced. The perception of improved developer productivity, in the case of practising development within virtual machine environments, comes from the developers no longer taking resource constraints and usage into consideration during design and development; this allows the product to be completed faster but it results in increases to the end user's hardware requirements to compensate. (Wikipedia)
Consider minimalism as your development practice (1, 2, 3, 4)
Many developers have already spoken about embracing Minimalism as a practice in software engineering. Consider reviewing the Minifesto, the Wikipedia page for an overview on Minimalism in computing or read some of the articles below:
Should we actually still build native applications or do everything in the browser and rather assume better connectivity (so that resources can be used centrally and hence more efficiently)
Chrome vs. Native App - what’s the difference with the same scenario? Word vs. Google Docs
The studies before are mostly applicable for desktop software applications what about software-as-a-service?
Sustainability vs. resource efficiency (principles, e.g. open source vs. techniques)
References
Sustainable software products — Towards assessment criteria for resource and energy efficiency - Eva Kern, Lorenz M. Hilty, Achim Guldner, Yuliyan V. Maksimov, Andreas Filler , Jens Gröger, Stefan Naumann (https://doi.org/10.1016/j.future.2018.02.044)