[openstack-dev] [cross-project][quotas][delimiter]My thoughts on how Delimiter uses generation-id for sequencing

Jay Pipes jaypipes at gmail.com
Sun May 15 18:11:29 UTC 2016


Amrith, your code in SQL statements below is identical to mine in Python 
with two exceptions that make your code less scalable and more problematic:

1) You have your generation on a "resources" table instead of a 
"consumers" table, which means you have an extremely high likelihood of 
needing to retry your UPDATE statements and SELECTs because all 
consumers in the system share a single serialization point (the 
resources.generation field for the resource class).

2) The Delimiter library (i.e. "the quota system") *should not own 
resources*. Resources are owned by the individual services themselves, 
not some separate quota library. The quota library does not have 
knowledge of nor access to the allocations, inventories, or 
resource_providers database tables. In fact, the quota library should 
not assume *anything* about how usage and inventory information is 
stored or even that it is in a transactional RDBMS.

The Python code I used as an example was deliberately trying to keep the 
quota library as a consistent interface for how to deal with the 
check-consume pattern without needing the quota library to know anything 
at all about how the actual resource usage and inventory information was 
stored.

Best,
-jay

On 05/15/2016 01:55 PM, Amrith Kumar wrote:
> Qijing,
>
> As a simple example, let's assume that I use this schema. I realize that
> it does not provide the resource provider thing that Jay talked about in
> a previous (couple of weeks ago) email, but I believe that it serves to
> illustrate how the generations are used.
>
> create table resources (
>      resource_id varchar(36) primary key,
>      resource    varchar(32),
>      generation  integer
> ) engine=innodb;
>
> create table allocations (
>         consumer_id       varchar(36),
>         resource_id       varchar(36),
>         amount            integer,
>         foreign key (resource_id)
>                 references resources(resource_id)
> ) engine=innodb;
>
> I've also populated it with this sample data.
>
> insert into resources values ('b587d300-1a94-11e6-8478-000c291e9f7b',
> 'memory', 3);
> insert into resources values ('b587ddb1-1a94-11e6-8478-000c291e9f7b',
> 'cpu', 3);
> insert into resources values ('b587de7d-1a94-11e6-8478-000c291e9f7b',
> 'disk', 3);
>
> insert into allocations values
> ( '61412e76-1a95-11e6-8478-000c291e9f7b',
> 'b587d300-1a94-11e6-8478-000c291e9f7b', 1024 ),
> ( '61412e76-1a95-11e6-8478-000c291e9f7b',
> 'b587ddb1-1a94-11e6-8478-000c291e9f7b',    6 ),
> ( '61412e76-1a95-11e6-8478-000c291e9f7b',
> 'b587de7d-1a94-11e6-8478-000c291e9f7b',10240 ),
> ( '61412e76-1a95-11e6-8478-000c291e9f7b',
> 'b587d300-1a94-11e6-8478-000c291e9f7b', 2048 ),
> ( '61412e76-1a95-11e6-8478-000c291e9f7b',
> 'b587ddb1-1a94-11e6-8478-000c291e9f7b',    2 ),
> ( '61412e76-1a95-11e6-8478-000c291e9f7b',
> 'b587de7d-1a94-11e6-8478-000c291e9f7b',  512 ),
> ( 'be03c4f7-1a96-11e6-8478-000c291e9f7b',
> 'b587d300-1a94-11e6-8478-000c291e9f7b', 2048 ),
> ( 'be03c4f7-1a96-11e6-8478-000c291e9f7b',
> 'b587ddb1-1a94-11e6-8478-000c291e9f7b',    2 ),
> ( 'be03c4f7-1a96-11e6-8478-000c291e9f7b',
> 'b587de7d-1a94-11e6-8478-000c291e9f7b',  512 );
>
>
> That gives me this as a starting point.
>
> mysql> select distinct resource from resources;
> +----------+
> | resource |
> +----------+
> | memory   |
> | cpu      |
> | disk     |
> +----------+
> 3 rows in set (0.00 sec)
>
> mysql> select distinct consumer_id from allocations;
> +--------------------------------------+
> | consumer_id                          |
> +--------------------------------------+
> | 61412e76-1a95-11e6-8478-000c291e9f7b |
> | be03c4f7-1a96-11e6-8478-000c291e9f7b |
> +--------------------------------------+
> 2 rows in set (0.00 sec)
>
>
> -----
>
> Assume that the consumer (61412e76-1a95-11e6-8478-000c291e9f7b) has a
> CPU quota of 12, we can see that the user has not yet hit his quota.
>
> mysql> select sum(amount) from resources, allocations where
> resources.resource_id = allocations.resource_id and resources.resource =
> 'cpu' and consumer_id = '61412e76-1a95-11e6-8478-000c291e9f7b';
> +-------------+
> | sum(amount) |
> +-------------+
> |           8 |
> +-------------+
> 1 row in set (0.00 sec)
>
>
> In this situation, assume that this consumer wishes to consume two
> CPU's. Here's what quota library would do.
>
> The caller of quota library would provide something like:
>
> 	consumer_id: 61412e76-1a95-11e6-8478-000c291e9f7b
> 	resource: cpu
> 	quota: 12
> 	request: 2
>
> Here's what the quota library would do.
>
> mysql> select resources.resource_id, generation, sum(amount) from
> resources, allocations where resources.resource_id =
> allocations.resource_id and resources.resource = 'cpu' and consumer_id =
> '61412e76-1a95-11e6-8478-000c291e9f7b' group by resources.resource_id,
> generation\g
> +--------------------------------------+------------+-------------+
> | resource_id                          | generation | sum(amount) |
> +--------------------------------------+------------+-------------+
> | b587ddb1-1a94-11e6-8478-000c291e9f7b |          3 |           8 |
> +--------------------------------------+------------+-------------+
> 1 row in set (0.00 sec)
>
> -- it can now determine that the quota of 12 won't be violated by
> allocating two more. So it goes ahead and does this.
>
> mysql> begin;
> Query OK, 0 rows affected (0.00 sec)
>
> mysql> insert into allocations values
> ( '61412e76-1a95-11e6-8478-000c291e9f7b',
> 'b587ddb1-1a94-11e6-8478-000c291e9f7b', 2);
> Query OK, 1 row affected (0.00 sec)
>
> And then does this:
>
> mysql> update resources set generation = generation + 1
>      -> where resource_id = 'b587ddb1-1a94-11e6-8478-000c291e9f7b'
>      -> and generation = 3;
> Query OK, 1 row affected (0.00 sec)
> Rows matched: 1  Changed: 1  Warnings: 0
>
> It observes that 1 row was matched, so the allocation succeeded and
> therefore it does this.
>
> mysql> commit;
> Query OK, 0 rows affected (0.01 sec)
>
> -------
>
> Assume now that consumer 'be03c4f7-1a96-11e6-8478-000c291e9f7b' with a
> cpu quota of 50 comes along and wants 4 more. The library does this.
>
>
> mysql> select resources.resource_id, generation, sum(amount) from
> resources, allocations where resources.resource_id =
> allocations.resource_id and resources.resource = 'cpu' and consumer_id =
> 'be03c4f7-1a96-11e6-8478-000c291e9f7b' group by resources.resource_id,
> generation;
> +--------------------------------------+------------+-------------+
> | resource_id                          | generation | sum(amount) |
> +--------------------------------------+------------+-------------+
> | b587ddb1-1a94-11e6-8478-000c291e9f7b |          4 |           2 |
> +--------------------------------------+------------+-------------+
> 1 row in set (0.00 sec)
>
>
> Clearly the user has only two cores in use and 4 more will not violate
> the quota.
>
> Therefore the quota library does this.
>
> mysql> begin;
> Query OK, 0 rows affected (0.00 sec)
>
> mysql> insert into allocations values (
>      -> 'be03c4f7-1a96-11e6-8478-000c291e9f7b',
>      -> 'b587ddb1-1a94-11e6-8478-000c291e9f7b',
>      -> 4);
> Query OK, 1 row affected (0.00 sec)
>
> mysql> update resources set generation = generation + 1 where
> resource_id = 'b587ddb1-1a94-11e6-8478-000c291e9f7b' and generation = 4;
> Query OK, 0 rows affected (0.00 sec)
> Rows matched: 0  Changed: 0  Warnings: 0
>
> Since, in the period of time between the select of the generations and
> the time when the quota library decided to finish this allocation, some
> other requester had made an allocation of CPU, the query updated 0 rows.
> This is an error and therefore the quota library will rollback and
> retry.
>
> mysql> rollback;
> Query OK, 0 rows affected (0.01 sec)
>
> mysql> select resources.resource_id, generation, sum(amount) from
> resources, allocations where resources.resource_id =
> allocations.resource_id and resources.resource = 'cpu' and consumer_id =
> 'be03c4f7-1a96-11e6-8478-000c291e9f7b' group by resources.resource_id,
> generation;
> +--------------------------------------+------------+-------------+
> | resource_id                          | generation | sum(amount) |
> +--------------------------------------+------------+-------------+
> | b587ddb1-1a94-11e6-8478-000c291e9f7b |          5 |           2 |
> +--------------------------------------+------------+-------------+
> 1 row in set (0.00 sec)
>
>
> -- it sees that the generation is now 5
>
> mysql> begin;
> Query OK, 0 rows affected (0.00 sec)
>
> mysql> insert into allocations values
> ( 'be03c4f7-1a96-11e6-8478-000c291e9f7b',
> 'b587ddb1-1a94-11e6-8478-000c291e9f7b', 4);
> Query OK, 1 row affected (0.00 sec)
>
> mysql> update resources set generation = generation + 1 where
> resource_id = 'b587ddb1-1a94-11e6-8478-000c291e9f7b' and generation = 5;
> Query OK, 1 row affected (0.00 sec)
> Rows matched: 1  Changed: 1  Warnings: 0
>
> This time it works, 1 row was updated, so we commit.
>
> mysql> commit;
> Query OK, 0 rows affected (0.01 sec)
>
>
> Treat freeing of a resource identically. First delete rows from
> allocations and in the same transaction attempt to update resources
> again. If you get a row updated, commit and if no row is updated,
> rollback and try again.
>
> Hope this helps!
>
> -amrith
>
>
> On Sun, 2016-05-15 at 01:16 -0700, Qijing Li wrote:
>> Hi Vilobh,
>>
>> Here is my thoughts on how Delimiter uses generation-id to guarantee
>>   sequencing. Please correct me if I understand it wrong.
>>
>> First, the Delimiter need to introduce another model ResourceProvider
>> who has two attributes:
>>
>>        * resource_id
>>        * generation_id
>>
>> The followings are the steps of how to consume a quota:
>>
>> Step 1. Check if there is enough available quota
>>
>>      If yes, then get the $generation_id  by querying the model
>> ResourceProvider with the given resource_id which is the point in time
>> view of resource usage.
>>
>>      If no, terminate the process of consuming the quota and return the
>> message of “No enough quotas available."
>>
>> Step 2. Consume the quota.
>>
>>     2.1 Begin transaction
>>
>>     2.2 Update the QuotaUsage model: QuotaUsage.in_use =
>> QuotaUsage.in_use + amount of quota requested.
>>
>>     2.3 Get the $generation_id by querying the ResourceProvider by the
>> given resource_id.
>>
>>          If the $generation_id is larger than the $generation_id in
>> Step 1, then roll back transaction and GOTO step 1.
>>
>>             this case means there is someone else has changed the
>> QuotaUsage during this process.
>>
>>          If the $generation_id is the same as the $generation_id in
>> Step 1, then increase the ResourceProvider.generation_id by one and
>>
>>          Commit the transaction. Done!
>>
>>          Note: no case the $generation_id is less than the
>> $generation_id in Step 1 because the $generation_id is nondecreasing.
>>
>>
>> — Qijing
>>
>>
>> __________________________________________________________________________
>> OpenStack Development Mailing List (not for usage questions)
>> Unsubscribe: OpenStack-dev-request at lists.openstack.org?subject:unsubscribe
>> http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev
>
>
>
> __________________________________________________________________________
> OpenStack Development Mailing List (not for usage questions)
> Unsubscribe: OpenStack-dev-request at lists.openstack.org?subject:unsubscribe
> http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev
>



More information about the OpenStack-dev mailing list