A talk about indexes

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
good afternoon okay so this is going to be a talk about indexes a little bit obvious but yes and it's introductory turn other words we're looking at the basics of it we're not going to go into the the nitty-gritty details of exactly Harper's grace stores indexes on a physical layer we're looking at it at how the general concepts are going to look at the the different types of indexes wind-based to use each one look at a couple of examples of how to create indexes how to best use them to take advantage of the indexing within post grace so first thing a bit about myself I'm a director toupees systems it's a company that does medical aid switching it's about four and a bit years old now and we use Linux and Postgres the database that backs our system is post grace we do a bit of a bit of an interesting thing there with multi master replication I spoke about a bit about it last year and we'll probably revisit that early in the year or twos time our systems architect a DBA SS admin and a tinkerer kind of do just about anything that I have to do okay so first off what is an index it basically in simple terms helps the database find data within a table because without an index you'd have to scan the table from beginning to end to look for the values you're looking for an index just makes it faster it an index is by definition something that's very fast to search through to get you to look for a value which then points to the places in that in the tape in the table store where you can find a set of points and say look in this block of this file and that way you'll find the records so then it's very easy to actually go and obtain that data an index can also be used to aid with the sorting of data it isn't only used to look data up but an index or certain types of indexes are implicitly sorted in order to speed the searching up that with that you can actually if you query the index you already get the data in a pre sorted manner so you can actually help with the sorting of the data and it can be stored in an easily search Chabal in an easy searchable way I already mentioned that one but all indexes take up a certain amount of additional space now in general terms that's very negligible they don't take up a lot but you just have to be aware that you can't just create an index for free there is some amount of additional overhead both in the creation maintenance and in storage for it but databases will not use an index if the table is small enough because in order to get a value out of a table through an index you've got first to read on the index then a read from a table so if the tables too short you're actually going to increase your amount of time to obtain the data by first going through the index to get tooth to get to the table the tables one enough you just read the table but the query optimizer in Postgres will make that decision as to which to use so sometimes when you have an indexed you'll look at the database and it'll say using a sequential scan you'll get wise not using the index because it would actually be more or less efficient more inefficient to use the index as opposed to just reading the whole table especially if you've got something like SSDs back in your back in your heart your databases it's that that point at where top server from doing a screen show scan to using the index is often a lot higher okay so just to rehash on that one why do we need indexes to allow our database to read less data from the disk so instead of reading everything we can read just a little bit in order to find it in certain cases that allows you to read blocks of nearby data from the hop on the disk which makes it even more efficient so if you know that your data is in a certain range you can request stuff from the database in a certain block of data and then when it issues the read command to the to the drive it says read this range of data and it reads it back just fine okay it can read data that's pre-sorted we've really spoken about that one okay so the types of indexes in Postgres we've got a b-tree that's the default that's the most common it's the most used it's basically the normal type of index you have we've got a drin index the just index especially partitioned just index brennon index which is a block range index and then the hash index but of note on the hash index it's not safe to use before Postgres team so up to a post-race nine the hashes when hash indexes were not committed to the to the well to the right ahead log and that means that if you had streaming replication for master to slave that the hash index would not persist through to the slave properly and then if you did a failover to it the index would be in an invalid state the database would attempt to use it and wouldn't fail on it and also if you had to do a restore from a right ahead log then your hash index would be inoperative okay so let's look at the first type of index a balance to tree that's a balance tree all right essentially you have us you have a starting node from there you either go left or right and essentially the okay this example is probably not so great because the they fall on the same level but a b-tree within the database will not be more than one distance across the entire into the entire I think Postgres will keep the tree balanced as it goes through so that just means that it's fast it's it's equally fast to search for anything anyway in it okay so all leaves are the same distance from the root but my example only had two per node but they can be multiple children per node and that's how it helps keep it balanced the B tree is the most common type of index and the default so if you don't specify using this index type then post grace will default to using B tree so the operators that that you can use in a way close that will trigger the use of a B tree is less than less than equal to equal to greater than equal to and greater then so the normal equality and less than greater than tarpor operators and yes they are more we're going to get to other tops the luck and the the tilde in post grace is to use a regular expression who does not know what a regular expression is okay that's good so if you want to use a regular expression as your match you can use too otherwise you can use luck to use the sequel style but it will only use the index where a lacking for a tilde if the pattern on so another it's column luck and whatever's on the right of it is a constant in other words it's a fixed fixed string or it's anchored at the beginning with text so for example column name luck few percent will use the index but column name like percent foo will not use it okay and the reason for that is if you take the first example the three percent it will go down the index look for if go down to look for O go down look for a and then grab everything under there as a result in that but with the first one you say it's going to end in food so where do you start in the tree you'd have to traverse the entire tree to look for every node that ends in foo so that's why you can't use a b-tree for that there is another type of index you can use with an extension that makes that problem go away the case insensitive variant the our Lac and the tilde star well you can use the index but only if the first character of the pattern is a non alphabetic character in other words a number or a punctuation of sorts if it uses a normal character that can that has an upper or lower case then it will not use the index so the b-trees can also use the between and the in operators and that's just because with the between it's essentially the same as a greater than equal to and a less than equal to so it's able to extrapolate from an index and the same thing with an in it's just a whole bunch of equals it's able to use the is null as well as is not null operation operators and with with indexes you can also specify whether you want the nulls at the beginning or at the end of the index so if you know that you're doing a lot of is null checks then you can actually specify at nulls first and then it's it sort of groups them a little bit better and it's the good general purpose index if you need an index it's probably a b-tree the next type of index we're looking at is the gen index and that stands for generalized inverted index don't ask why just accept that the underlying structure of eternia index is somewhat similar to a b-tree and it's good for data that contains multiple values so and so an array it's got multiple values the journey is able to index each individual one the Jason B taps where you've got key value pairs the drink index indexes those keys and values very well because it's able to handle and understand the multiple data types H store which is sort of an antique version of the structure that eventually became Jason B also can do that could has also multiple tabs ts vector is for full-text searching again and then range tabs are not going to go into that one in too much detail but it can also handle multiple values certain index is perfect for that and each in each index within the or each yeah there's an index entry in within the index for each and every possible value that's there so it's essentially taking a b-tree and just making it a lot bigger so it's able to test for the presence of a specific value within within the structure that you're doing Jenny indexes are the ones that are preferred for text searches and one of the more common uses for it is the trigram extension and we use a trigram with a drin then you're able to do text searches within within it within your database so if you have a so if you have a field that means there's a bunch of just descriptions on something you can use the trigram extension and Jenny indexes to build up that the all the possible values within there so using the example the words the trigram extension breaks it up into parts that are no more than three characters long and it paths each word with two spaces at the beginning and one at the end so the first value at index is his space space t in space th then th e then H E space now it's handled the first word so the second word it has it as space based W space W o and so on all the way to so if you search for something that lack percent word percent when it when it runs through the trigram index it breaks that one up again and then comes and matches using the dryn index and it will match the the presence of the words contains word so trinny indexes though because of this are generally a little bit slower to build because their indexing a bit more but they are very fast to use we use indexes with a trigram to match descriptions of medical products and we can search through almost anything and it happens in about 20 to 30 milliseconds and that's most of the setec region so Zambian South of that so even all of that it matches it incredibly quickly but because it's indexing more it often takes up a little bit more space on the disk obviously if you're storing each individual possible value within within an array within a Jason B or using trigram where it breaks everything up it does take a little bit more Gini index has support a whole bunch of other operators the first one the list then at is means is contained by the next one means contains then equals is obvious the end and means overlaps and you're not going to go into the rest of them they just are getting complicated but obviously if you have a data type that uses it you will have a specific need to use one of those operators certain contrib modules those are extensions like trigram can add operators certain data types also have varying operators so if you have a text one you might not have the question mark question mark and percent and so on tops and the trigram one adds a couple of extra operators the percent less than percent the if you were at the post just talked you would actually have seen the one that the the brace less then minus writer then it's sometimes called the taffeta operator and there's a good reason for that the next index top the just index that's a generalized search it's not a single type of index where b-tree and gin were basically b-tree indexes under the hood the Geist index is any different type of combination or different strategies for for indexing can be utilized by adjust it's dependent on what tight data type you're indexing and what module and operator class do you spare so far for it and just indexes are the based for values that that overlap in other words where you've got a lot of values that are very similar or the same they can overlap or we'll get to a little bit later for GIS data we you've got rained or areas that overlap each other it can also be used for take searches like with gin and it has a slightly smaller disk size but it has a slower performance and the reason is that just indexes they return more values and then we have to filter them so with gin and with b-tree when you ask an index for a value you get the exact answer back with just you will get back all your results plus a few extra rows and then the query planner has to add an extra layer in there to filter out those extra rows so you don't notice it from you from the perspective of the results you always get the correct results back the difference is that internally because the just index returns more because it's actually searching a little bit it's searches over a smaller space it has a lot of false positives that have to be removed but it is based for the full geometry or for GIS data mention that one and again we've got even more operators to choose from not even gonna mention half of them and again they vary based on the operator class of the index the data type as well as contrib modules depending what you indexing hiding which module you using for for accessing the data those operators all change the next type of index is the space partition just index and that's the right from research from Purdue University and essentially what it is is a textual entire data set and petitions it into chunks and then essentially indexes it below that it's not an equal balance tree like a bee tree or journal index it's quadtrees KD trees radix trees whatever is appropriate for that certain incredibly unbalanced index and it's specifically good for large data sets that have a natural clustering so telephone numbers for instance we have a lot of telephone numbers that are all oh one one or oh one two or 2 1 or 0 8 2 yet we don't have a lot of them that are in the Oh 1 8 or 1 9 I mean we even have those so we have this this means clustering at one point and then if you have a look at say oh one to number you'll have a clustering again at 3:42 you'll have a clustering at 664 that sort of thing we each regional area has had a lot of clustering of and certain ranges and RP addresses on another one we end up clustering around each octet where certain updates like the 192.168 that occurs a lot or in south africa a lot of our RP start with either one or five or worth forty one so especially partitioned a space petitioned just index is based for that sort of data that has clustering and that's why it's not a balanced tree so you start off with let's take an IP address for t1 and then it will essentially go to that node and then dot one seven 8.15 to dot and so on and even more weird looking operators every time we seem to hit another one it seems to be even more operators again I'm not even going to attempt to explain half of them I didn't even know when we start hitting the till the ones I didn't even know what they're for because some of them offers very specific data types and that but again if you're using a space partition index you go look it up on the reference material it'll tell you exactly when to use which ones for what for what data tar for everything but those are all applicable to space partition index all right so the brain index is a block range index and this is actually very nice index if your data is ordered and the reason is what it basically does is it takes blocks of data works out in simplistic terms what the min and max range is within that within that block of pages and only cares about what that min and max is so instead of storing the value for a million rows you must all the men in max 14,000 so if I'm looking for a let's just simplistically say indexed a whole bunch of integers and I'm looking for anything that's 50,000 it wouldn't have to search everything to find it it would say okay well it's all the values that are possibly 50,000 are in these blocks and then the database will going to fetch that range of blocks and then filter it out from there it only works for data that's like large data sets that are ordered so if your data is not ordered it will not work so for instance a sales table where your job your sales come in and they're coming in date order or okay that's probably the best example right now so anything that's in perfect order so you always know that the order if you had to sort the table and the native storage of that data would be in the correct order then you're fine you can use it I've been to the min max if your data is not ordered though then essentially every single one of these blocks these ranges of rows will have a min and max like that so then as you've seen cheap you search for 50,000 is going to say it's everywhere because it wouldn't know new table pages are not always up don't always update the range info so you either need to if you're doing a lot of inserts either need to call the Brin samaras range function then runs through and updates the index or you can wait for a vacuum or an auto vacuum to kick off and then it will run it or you can set the auto summarize parameter there's a slight performance implication to running murres ation everytime but it does help with the brain index then we back to simple operators just the normal ones because in this case if you're looking for a range the database is storing a range if for the index if you're looking for a range it's possible and then there's a couple of other data types that add a few more like there are net for the RP addresses it as a couple more and again I'm going to reiterate this absolutely terrible performance if the data is not ordered in fact you probably found that the query optimizer will just simply ignore the indexes existence if it thinks that it's indexing stuff across a to broader range then the hash index and this is the one that I said be careful with for anything before Postgres ten instead of storing the value itself within the index it stores a hash of the index so it's very similar to a b-tree but it can sometimes be faster than a b-tree especially on slightly larger data sets because it's optimized just to a hash instead of storing different data types but because of that it can only handle inequality operator so you cannot use less than greater than or anything else the only option you've got is the equals operator and that's important do not use it before Postgres 10 if you're using Postgres 10 or using Postgres 11 release candidate or whatever and soon to upgrade feel free to use it but compare compare b3 and hash you often find that you don't get a major performance boost out of the hash whereas b-tree will just work fine okay so this makes the question which of these indexes do we use I mean we've had a whole lot of them and you think I can well b-tree it was a drin or what we use it for so bee tree works for most situations and in fact probably 99% EP indexes will be an issue in doing GIS data 99% EP indexes will be b-tree if you're wanting to index arrays or Jason B or use the PG trigram extension consider using the the gen index it works the best for those if you're using GIS or job three data then use the just index tap you can also use trigram there the general preference is towards the journal index but large datasets have ordered and clustered data then spaced petitions just works best in that scenario burn index for large ordered datasets where the data is in perfect order within the database or reasonably perfect order and the database is able to define min and Max ranges for each block just a trick on that one if you want to get your data ordered you can create a b-tree index on it and then plaster use the cluster command it won't lock the entire table but it's essentially the same as doing a full vacuum but in this in the same time it's going to make sure that the data is in perfect order when it's written to disk it won't keep the order obviously because when you update a row some value somewhere gets marked as available for reuse so if you want to keep your data in order you either never update or delete or you just cluster and you handle the lock and then the hash index when you only care about an equality operator if you only care about an equals in your way close and you're not worried about ranges then a hash works fun okay so let's have a look at how we utilize indexes and winds when certain indexes will get used to the won't get used and so on so simplistic case a single column index we say create index it's name on table column one that's equivalent to the next one create index name on table using b-tree so if you don't say using b3 the database will handle that as a default the alternative for instances you could say using hash or using gin or using just and to use that index select star from table where Col 1 equals a that's an easy one that's sort of databases 101 but that's fun a multi column index or a compound index or composite index or whichever term you want to use for it you say create index name on table call one chemical two and internally the database simplistically concatenates the values together so internally it's only indexing one value that sourced from multiple columns and to use that you can say select star from table where column one equals a and column two equals B then it will again simplistically say I'm gonna go look in the index for a B it's got a little bit more intelligence this that it doesn't look for column one with a B and column two empty but simplistically speaking the thing with a multi column index is you can still use it even if you're not using all of the values that you indexed so you can say select from table where column one equals a and it will still use this index because because the values are concatenated it's able to search through so it's similar to doing a lakh a percent it's somewhat equivalent to that again I'm oversimplifying here but if you have a multi column index you always have to specify them in order so in this case the weight Clause is only using column two it's not using the first one that we specified when we created the index of column one so in this case it will not use the index you can have 20 columns they okay if you specify the first 15 it will use the index specify the first one it'll still use the index and if you specify the first one and the 20th one it will ignore the 20th one when it uses the index will only use the first one so with the multi column index always make sure you specified or you specify the first column in your way clause that was specified when you created the index multi-column indexes are only supported by b-tree June just and bring in big stuffs you can't use it for hash or space partitions an index that just indexes and you can have up to 32 fields in a multi column index or the that point it starts getting a bit large to actually run the index so the next thing we can look at is a partial index and that's where when we create the index we give it away clause so in this case it will create an index of column ones values but only in the case where the rows column two value is greater than 100 okay and we there comes in very handy is when you use a query like that where you say select from table where column 2 is greater than 100 so then the awkward optimizer says but these way clauses match I can use that index straight out of the box and because I'm saying select star it returns that entire index and utilizes it you can use expressions in your when you create the thing so instead of just saying create index on table column 1 you can say create index on table lower column 1 so the lower case value for all of them and then if you use the same thing in the way Clause it will match that so if you just say column 1 equals value on the normal index it will use it in this case the lower column 1 it will utilize that index and you can even do it with string concatenation so first name concatenate space concatenate last name and use that as your your index and your where clause it won't actually match that and utilize that index so if you do that you can use any built-in or user-defined function you can do arithmetic expressions string concatenation but the important thing to note if you use functions they must be immutable now functions can be stable immutable and volatile so something that is volatile means that every single time you run it it'll give you a different answer okay so obviously you can't use that for an index because if I run it twice within the same query I'm gonna get a different answer something that's stable I've got to remember this one carefully something that stable will return the same result within the same query or same transaction I think and then immutable will always return the exact same answer no matter what so if you have an add function that takes two values one and two you know that the answers always going to be three that's immutable and something like the timestamp or now function is stable because it doesn't change within the same run you can use it multiple times in one query and it returns that same value every time but on the next query it will give you a different answer so for an index it has to be immutable because when you build the index and when you use it it has to know that the values would be same so in other words it's effectively a constant okay so if we take just go back to the the simple index we can order it applies to multi column you can do a unique index so you can say create unique index and not only will that build an index it will also enforce uniqueness so if you try and insert another row another row in the table with a column one value that already exists in the index this index will kick it out it will say you're violating the unique constraint you can't do it and only b-trees can be declared as unique indexes can be built concurrently when you build a normal index it will lock the table for the duration of the index build but you can build it concurrently and in that case it doesn't take out a rat lock against the table it creates an index marks it is invalid and then starts building the index in the background when it's done it will go back and handle any changes that have occurred since it started that index once it's done then it flips the index to mark it as valid and then the query optimizer will will be able to use it it query optimizer query planner won't use a invalid index it often takes a little bit longer because it's got to run through the data and they've got to run through it again and there's contention there's other locks that are happening versus when you just build an index normally it's got an exclusive lock on the whole table and can just read everything just be aware that if you're using versions of post grace less than that I'm not going to list them all if you're using anything in that major block and you're using that just be aware that there is a possible build corruption on when you're using concurrent indexes the last I checked there wasn't any known instances of it but it doesn't mean that it won't happen all right so if we have a look at constraints that become indexes the most obvious one is a primary key if you declare a primary key on a table it will implicitly create an index in the background for you all right so if you do a look-up on the primary key there's an index de packet exclusion constraints that's a discussion for another entire talk actually there was a talk on constraints yep and that was actually handled a so if you do alter table add constraint exclude using it will also build an index in that case you actually specify the top sometimes you actually want to use adjust or adjourn as building that constraint because using ranges most often in that case and then unique constraints that also builds in your clustered index so that begs the question why have unique constraints and unique indexes if the unique constraints create a unique index for you in the background first off there are functionally symbolizing with if you build one or the other they will act the same and again a unique constraint will create a unique index in the background and the reason we have both is because a unique index can have a where clause where unique constraint cannot so if you have a unique constraint it's on an entire table the entire column as a whole whereas if you want to have a conditional way unique constraint so you want something unique where some other column value is a certain value then you can utilize a unique index but for most cases I would recommend that you use a unique constraint okay unless you need to do a partial index with the where clause in the index then use a unique index don't create both it's a waste of space and time I say space because you know I've got two indexes I say time because there's now two unique constraints to check two indexes to maintain two indexes to run through the whole time if you have so let's say you've got an existing table but you want to add a unique constraint to it and a unique constraint will lock the table so how do we edit unique constraint to a table without locking it this is the simple way to do it you create a unique index concurrently let that finish building then you alter table add unique constraint using that index it then won't build its own index it will just use the one that you already created so there's a nice neat little trick to add a unique constraint to a table later down the road when you've actually got too many rows then you can't afford the lock foreign key constraints do not create an automatic index so this is the one constraint taught that does not and the reason is because there lot of index types to choose from if you're talking unique doesn't matter b-trees the only one that supports unique so that's fine if you're doing exclude using you telling it the exact type of index you want to use it knows what to build with a firing key constraint do you build a b-tree do bullet gin do you build adjust some key terms you can use more than one sometimes that geometry it's obvious but with other types you don't know although you shouldn't be doing foreign key with geometry but I will suggest you consider adding one okay and the reason for that is it improves the speed of delete and update operation on the reference table so if you have table one with a foreign key to table two and you go and delete a row from Table two with a foreign key it goes back to Table one and make sure that that row is not referenced in table one if you don't have an index on table one for that column it's going to sequentially scan an entire table to make sure that you're not violating the foreign key but deleting a row in table 2 so if you create a foreign key constraint create an index that's just the one reason the other reason is when it starts merged running or hashed running between the two tables it's better to do it when you put an index on both sides it's the query planner with more choice and okay an index only scan so we spoken about how an index can you look up a value in index and there it goes and finds it in the table but if you are if you for instance create index name on table column one and you say select column one from table the database will scan that caught that index and return it as is without ever touching the data in the table itself it's got no reason to I've asked for the exact thing that's stored in the index and the index is very fast so it queries it and returns that value so if you if you ever do and explain on a query and you see index early scan that's what's happened you can order by index I mentioned that indexes are sorted and that they give you an ordering so if you say select star from table order bar column one ascending then it really gets the data as it scans the index it's getting it in the in the sorted order already so then as its querying the data from the table that's already got it in in a plot or an implicit sorted order only works with a b-tree index you can also do it with with multi column or compound or composite whatever term you want to use and you can specify different orderings so you can say column one ascending column two descending and then have it in the order by and it will again use the index just a note check where you where you specify your column name in a way clause so for instance if you say select star from sales where now is greater than the sale date plus 30 days it will not use the index the second one where you say select star from sales where the date is greater than now - 30 days functionally identical will return the same results but only the second one will use the index and the reason is and the second one the value on the one side or other in the your case on the right side of the greater than is a constant because now is stable it returns that it works out an exact time step becomes a constant and then it's able to utilize it where's the first one we are saying take whatever the column value is add 30 days and compare it to NASA for every single row it has to go and fetch that US and then do the comparison do not over index having too many indexes will slow down your insert update and delete operations because every time it has to go into every index and update them check wheel where indexes are not being used so select star from PG stat user indexes have a look at the index scan tuple read and people fetch if they are 0 that index is probably not being used and try to combine your indexes where possible so if your indexing on column one and you could another one indexing on column one and column two the first ones are necessary just a note on the building of indexes in your post Chris torque on file the maintenance work main value is utilized for a couple of things within the database but one of them is for the building of indexes so if that value is too small then it's going to end up writing the entire value or the entire index to disk as a speci processing if you if you increase that value then it's able to store the entire index in memory as it's busy building it as opposed to constantly writing at the dusk but don't make maintenance work more than the amount of memory that would actually be available or your operators operating system is going to start swapping to disk and then it's effectively the same as having a low maintenance work me just a quick note on partitions tables and indexes before post Christine indexes had to be created on each of the child tables or tin and lower from 11 onwards the parent table will propagate that through to the children which is actually very nice if you have if you have partitioned tables you created once on the main definition and then every time of partitions created it creates the indexes with it so that's a nice thing that they've added now with 11 it was Laplace to unique indexes any questions dropping sorry you mentioned the fact that let me get my story straight that the indexes will be stored in my little class sorry the next will be stored in an ordered manner certain tops are certain types those ups what the term is it or is it in terms of the file structure that persky's uses that's going into a very deep level now I'm asking because you get a difference in spinning disk an SSD the thing is most indexes in any case are usually reading to memory okay and then they rate the energy plate that balance tree they take a b-tree they imbalance a b-tree structure within memory so first crystal always attempt to get the the indexes ofter scan into memory okay then we don't really care about how they are on dusk but indexes are written in a similar manner to how tables are stored there's just a little bit more metadata in to allow it to actually build the tree from that yes but if you've got a very large table or large data volumes and it's actually doing an index seek then obviously the way it's ordered on the disk itself is going to matter it yes but that's where the metadata comes in so the index so it I mean if that's really going into a deep level and you can have a lot of time but the I mean if we if we row if we just look at the table structure every row of data has a bunch of metadata that describes it as well an index has a little bit more to it and that's what would help post grace you saying she couldn't reconstruct the bee tree from this stuff no matter where it is on the disk because it would still that the disk storage doesn't stay ordered it's the structure that stays ordered if that sort of makes sense okay it's yeah it's a little bit too in-depth right now but I can talk to you but just now okay there's another question of the day okay so that's a challenging question because that's coming from David okay for the for the Brynn index talked about the importance of keeping of having ordered data but when you do updates in Postgres it will take your row somewhere else so that's what I said yeah if you have so even if it's not the Brynn index column that's updated if it's an update intensive or even moderately update intensive where a table is it best to avoid Brynn is this would be because then your data your physical storage on disk becomes out of order right if you're only ever inserting and never updating or deleting Brynn is perfect but if you updating then you creating these little gaps and in your range of start getting broader and broader and the index over time becomes useless okay thanks okay thank you very much [Applause]
Info
Channel: PostgresConf South Africa
Views: 4,303
Rating: undefined out of 5
Keywords: pgza, pgza18, MalcolmMcLean
Id: HAn1xu6_SW0
Channel Id: undefined
Length: 41min 54sec (2514 seconds)
Published: Fri Oct 12 2018
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.