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: [email protected]?subject:unsubscribe
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev



__________________________________________________________________________
OpenStack Development Mailing List (not for usage questions)
Unsubscribe: [email protected]?subject:unsubscribe
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev


__________________________________________________________________________
OpenStack Development Mailing List (not for usage questions)
Unsubscribe: [email protected]?subject:unsubscribe
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev

Reply via email to