{section: Introduction to Consumption Policies} A Consumption Policy is a policy expression that resides on a partitionable slot classad, as advertised by the startd on an HTCondor execute node, which governs the amount of resources used by a job match against that slot. Each _kind_ of resource (or resource "asset") has a corresponding Consumption Policy. In a typical partitionable slot (p-slot), three resources are always defined: Cpus, Memory and Disk, which might advertise Consumption Policies as configured in this simple example: {code} # enable use of consumption policies CONSUMPTION_POLICY = True # define a simple consumption policy # (note, "target" refers to the scope of the candidate job classad being considered for a match) CONSUMPTION_CPUS = target.RequestCpus CONSUMPTION_MEMORY = target.RequestMemory CONSUMPTION_DISK = target.RequestDisk {endcode} The HTCondor negotiator matchmaking logic is aware of a Consumption Policy (CP) detected on a p-slot. When a job matches against a p-slot with a CP, the amount of each resource dictated by its consumption policy is deducted from that p-slot. The p-slot then remains available to match with another job. In other words: *Consumption Policies allow multiple jobs to be matched against a single partitionable slot during a negotiation cycle*. When the HTCondor startd is allocating a claim to a new match, the same Consumption Policy expressions are also evaluated to determine the resources that are subtracted from the partitionable slot (and added to the corresponding new dynamic slot). {section: Motivations} Consumption Policies enable some appealing features, including support for pools with heterogeneous resource models, faster loading of partitionable slots, and more flexible utilization of accounting group quotas. {subsubsection: Heterogeneous Resource Models} Consumption Policies are configurable on a per-slot basis, which makes it straightforward to support multiple resource consumption models on a single pool. For example, the following is a cpu-centric resource consumption policy: {code} # cpus will generally be exhausted first: CONSUMPTION_CPUS = target.RequestCpus CONSUMPTION_MEMORY = quantize(target.RequestMemory, {32}) CONSUMPTION_DISK = quantize(target.RequestDisk, {128}) SLOT_WEIGHT = Cpus {endcode} Another slot might be configured with a memory-centric policy: {code} # memory will generally be exhausted first: CONSUMPTION_CPUS = 1 CONSUMPTION_MEMORY = quantize(target.RequestMemory, {1024}) CONSUMPTION_DISK = quantize(target.RequestDisk, {128}) SLOT_WEIGHT = floor(Memory / 1024) {endcode} Note that the slot weight expression is typically configured to correspond to the "most limiting" resource, and furthermore behaves as a _measure of the number of potential matches remaining on the partitionable slot_. {subsubsection: Fast Slot Loading} When the HTCondor negotiator matches a job against a partitionable slot configured with a Consumption Policy, it will deduct the resource assets (cpu, memory, etc) from that p-slot and keep it in the list. Therefore, a p-slot can be matched against multiple jobs in the same negotiation cycle. This allows p-slots to be fully loaded in a single cycle, instead of matching a single job per cycle. (Note: the CLAIM_PARTITIONABLE_LEFTOVERS feature is an alternative approach to faster p-slot loading, operating in the scheduler as opposed to the negotiator). {subsubsection: Flexible Quota Utilization} The cost of matching a job against a slot is traditionally the value of the SlotWeight expression. In a scenario where the slot weights of available p-slots are greater than an accounting group's quota, the jobs in that accounting group will be starved. However, when a p-slot with a Consumption Policy is matched then its match-cost is the _change_ in the SlotWeight value, from before the match to after. This means that a match is _only charged for the portion of the p-slot that it actually used_ (as measured by the SlotWeight expression), and so p-slots with many resources and possibly large SlotWeight values can generally be used by accounting groups with smaller quotas (and likewise by submitters having smaller fairshare values). {section: Consumption Policies and Accounting} As was mentioned above, the match cost for a job against a slot with a Consumption Policy is the _change_ in the SlotWeight value from before the match to after. It is this match cost value that is added to the corresponding submitter's usage in the HTCondor accountant. A useful way to think about accounting with Consumption Policies is that the standard role of SlotWeight is replaced with _change_ in SlotWeight. Also of note: because matching with Consumption Policies takes place in the negotiator, accounting of Concurrency Limits is also implicitly handled in the standard manner. {section: Consumption Policy Examples} In the preceding discussion, examples of a cpu-centric and a memory-centric Consumption Policy were provided. A few other examples are listed here. {subsubsection: Extensible Resources} All resource assets are required to have a Consumption Policy configured. This includes Extensible Resources, if any are also present: {code} # declare an extensible resource for a claim-based consumption policy MACHINE_RESOURCE_tokens = 3 # always consume 1 token, and none of anything else CONSUMPTION_TOKENS = 1 CONSUMPTION_CPUS = 0 CONSUMPTION_MEMORY = 0 CONSUMPTION_DISK = 0 # define cost in terms of available tokens for serving jobs SLOT_WEIGHT = Tokens {endcode} {subsubsection: emulate a static slot} This example uses a consumption policy to emulate static slot behavior {code} # consume all resources - emulate static slot CONSUMPTION_CPUS = TotalSlotCpus CONSUMPTION_MEMORY = TotalSlotMemory # Disk is unreliable -- TotalSlotDisk != Disk even on a virgin slot CONSUMPTION_DISK = floor(0.9 * TotalSlotDisk) # define weight to be 1, since this slot supports exactly one match SLOT_WEIGHT = 1 {endcode} {subsubsection: Multi-Centric Policy} What is "most limiting" resource might be contingent on job requirements. It is possible to define slot weight to take this into account, as in this example: {code} # Either Cpus or Memory might be limiting, depending on job requirements CONSUMPTION_CPUS = target.RequestCpus CONSUMPTION_MEMORY = quantize(target.RequestMemory, {256}) CONSUMPTION_DISK = quantize(target.RequestDisk, {128}) # define slot weight as minimum of remaining-match estimate based on either cpus or memory: SLOT_WEIGHT = ifThenElse(Cpus < floor(Memory/256), Cpus, floor(Memory/256)) {endcode} {subsubsection: Handle missing request attributes} RequestXxx attributes are not always guaranteed to be present, so Consumption Policy expressions should take this into account. For example a policy that involves Extensible Resources cannot assume jobs will be requesting such a resource. {code} # declare an extensible resource MACHINE_RESOURCE_actuators = 8 # policy for actuators that interprets missing request as 'zero', which is a good idiom for extensibe resources that many jobs are likely to not use or care about CONSUMPTION_ACTUATORS = ifThenElse(target.RequestActuators =!= undefined, target.RequestActuators, 0) {endcode}