Skip to content

REP-003: Package and Context Localisation #680

Open
@nerdvegas

Description

@nerdvegas

REP-003: Package and Context Localisation

Rez contexts contain references to the variants in their resolve, in the form of variant “handles”. A handle is just a metadata dict that uniquely identifies a variant and defines where it is located. For example, a variant handle typically contains the package name, package version, path to the package repository containing it, and index of the variant within that package. From its handle, the package definition can be retrieved, which is needed to configure the environment (for example, commands needs to be evaluated).

When resolved into an environment, a variant will typically reference its installed payload (its “root”) in some way - for example, by adding a “bin” subdirectory to PATH. The installed payload will reside within the package repository directory.

These are two examples of cases where use of a context requires access to external resources - either the package definitions (which would generally come from memcached in a production Rez install, but may also come from disk); and the package installation payload.

There are two different reasons why this may not be desirable:

  • We wish for a context to act in a standalone manner, so it can be run in isolation. For example, we would like a VM or container running a Rez-based micro service to not have to access memcached, nor to require a mount to shared file storage.
  • We wish for package payloads to be locally available, rather than having to be fetched over an nfs. This has the potential to significantly improve performance and decrease load on the filer.

Context Localisation

A “localised” or “deep” context would be one that contains entire copies of the package definitions its variants require. This would cause the following differences in behaviour (when compared with standard contexts):

  • The rxt file size would be larger (possibly significantly);
  • Changes to package definitions in their original package repositories would not take effect in the deep context;
  • The deep context would not require fetching of package definitions from memcached, and thus would source faster.

The following features are desirable, in order to give users as much control as possible, and to maintain backwards compatibility:

  • Use of deep contexts would be configurable, but would also be manually overridable in all cases where contexts are created. For example, rez-env would have a new —context-mode option (with “deep” and “shallow” choices), for use in combination with its —output option.
  • It should be possible to load a shallow context and convert it to a deep context, and vice versa (probably via the existing rez-context tool).

One thing that also needs to be considered is if it’s ever desirable to store deep contexts into memcached. This would result in more optimal loading of cached resolves, but at the cost of far more cache space - each package definition would effectively be stored many times over (once for each deep context they appear in). It may be better not to support this, and to instead wait for a port to redis, which supports multiple key retrieval (via its mget operation).

Package Localisation

The basic idea behind this feature is that, when a context is created, the variant payloads that that context uses are lazily copied into a local disk cache. However, there are a few points to consider:

  • We don’t necessarily want to copy all variant payloads, otherwise a context might take too long to create;
  • Copying package payloads may cause technical problems in some cases;
  • The user may need control over where the package cache resides - it may be too limiting to assume that there is just one cache.
  • How do we clean up old localised packages, so the cache doesn’t grow forever?

These points are now addressed in turn.

Localisation Mode

There should be a mode that determines how localisation behaves. Potential modes are:

  • Full. Localise all packages in the context, regardless of how long it takes;
  • Limit. Localise up to N packages at any one time;
  • Time. Localise packages until a time limit is reached;
  • None. Don’t localise anything.

If rez-env uses any of those last three modes, then a context may only have had some of its packages localised. That is ok though - more and more packages will be cumulatively localised with every resolve the user performs, and this should fairly rapidly result in full localisation anyway. If maximum localisation is priority, then “full” mode can be used, at the cost of context load time (and even then, this should rapidly improve anyway, as more and more packages are localised).

It’s worth noting the distinction between “none” mode localisation, and disabling localisation altogether. None mode would simply make use of any packages already localised; disabling localisation on the other hand would ignore the package cache completely.

Technical Problems

There are some instances where copying package payloads could be problematic:

  • The package is large, and could eat up too much home directory quota;
  • The package payload is not position independent (ie moving it will break it, because there are absolute references to itself, with itself; or, relative references to something outside of itself);
  • One package is not position independent relative to another (perhaps package A is rpathed to package B - moving B will break A’s symbol resolution).

Similar to the existing “non_relocatable” package attribute, there is need also for a “non_localisable” attribute. It would make sense for this to default to “non_relocatable”, as this is a very similar concept, and typically a non-relocatable package would also be non-localisable.

Describing the last case is a little more complex however. It may be necessary for another package attribute, that lists other packages that become non-localisable in their presence. For example, if the aforementioned A and B packages appear in a context, then B is not localisable, because of the presence of A.

Package Cache Location

It would make sense most of the time to use one configured package cache across the board, for any given user. However, there are cases where this doesn’t make sense. For example, to create a standalone context for use in a container, you would want to create a package cache specifically for that container.

Package cache location should be overridable in the Rez API and tools where appropriate. Furthermore, there should be a tool (rez-package-cache lets say) that allows for manually wrangling package caches. It would also be useful to be able to associate a context with a specific cache.

Examples of tool use:

# populate a specific cache using a context
]$ rez-package-cache —populate foo.rxt ~/mycache

# list variants in a cache
]$ rez-package-cache —list-variants ~/mycache

# create a context and associate it with a specific cache:
]$ Rez-env pkgA pkgB —package-cache ~/mycache —output bah.rxt

# create a copy of a context, associated with a different cache
]$ rez-package-cache —bake src.rxt dest.rxt ~/.othercache

# source a context and use a specific package cache
]$ Rez-env —input foo.rxt —package-cache ~/mycache — echo hello

If a context has a baked cache, perhaps it should fall back onto the globally configured cache, for any non-cached packages.

Cleaning up Old Cached Packages

There isn’t really a reliable way to 100% ensure that any given cached package is not still in use. Perhaps we could drop lock files into the cache to indicate they’re being used; but a context that unexpectedly aborts would undoubtedly cause these locks to be left behind, and their associated cached packages never to be deleted. Realistically we probably just want to delete based on date of last use, or a combination of that and max disk usage, or package count.

To allow for this, the rez-package-cache tool should be able to perform deletion based on the parameters described above. Furthermore, cached package directories should be touched on use so we can reliably say when they were last used.

To trigger the deletion, either a cron could be setup on workstations, or perhaps a configured setting would cause Rez itself to perform the cache deletions once every N configured days (or hours etc), each time a context is created or sourced.

Package Cache Structure

The package cache would be structured on disk like so:

{root}/foo/1.2.3/ab58fca5283cfbbbc3ca5680/
{root}/foo/1.2.3/3cfbbbc3ca5680ab58fca528/
{root}/bah/12.0.5/a258fc3ca5680a5283cfbbbc/

Each variant would be stored under a hash of its handle. To help with debugging, the leading directories would be the package name and version respectively. Different hashes within the same package and version represent different variants - either different variants within the same package, or variants from packages with the same name and version, but in a different repository.

When a cached copy of a variant in a context is found, its root is simply changed to that cached variant directory.

Package Immutability

One point to note is that caching is only useful if we can assume that the contents of a package do not change. Otherwise, the time spent verifying that the contents of a variant are the same as that in the cache, would negate the value of caching in the first place.

Fortunately, for the majority case in Rez - packages that have been released - immutability is already a property, and so these can be readily cached. The case where they cannot is local packages, which in practice get re-installed over all the time. So, package caching would be disabled for local packages (which makes sense anyway, because local packages typically already reside on the local disk, so there’s no point caching them).

Standalone Contexts

It’s already been mentioned that we would like to be able to create contexts that are completely standalone, for the purposes of running a micro service for example. In this case, we would like to localise both the context and the packages it uses. To do that, it would make sense to bake the package cache into the context, as shown earlier. However, it would also be practical to store this as a relative reference in the context, so we could copy both context and cache to a service VM or container together, rather than having to construct them in-place.

Here’s what that might look like:

]$ mkdir svr
]$ Rez-env pkgA pkgB —package-cache svr/pkg_cache —rel-package-cache —context-mode deep —output svr/svr.rxt
]$ rez-package-cache —populate svr/svr.rxt

This would:

  • Create a deep context (package definitions are embedded into the rxt);
  • Bake the relative path to the package cache, into the context;
  • Create a package cache (if it didn’t already exist);
  • Cache all the variants used by the context, into the cache.

We would then have an svr/ directory that we could copy to a server and run the server binary from. The only prerequisites would be:

  • The server has a system (platform, os etc) that is compatible with the context that was created;
  • Rez is installed on the server.

Running the service would simply involve:

]$ Rez-env —input svr/svr.rxt — my-server-command

Metadata

Metadata

Assignees

No one assigned

    Labels

    REPREPs are Rez enhancement proposals that need in-depth discussion

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions