Files
secondo/Algebras/Distributed/ExampleDistributedAlgebra
2026-01-23 17:03:45 +08:00

836 lines
27 KiB
Plaintext

/*
//paragraph [10] title: [{\Large \bf ] [}]
//characters [1] formula: [$] [$]
//[ae] [\"{a}]
//[oe] [\"{o}]
//[ue] [\"{u}]
//[ss] [{\ss}]
//[Ae] [\"{A}]
//[Oe] [\"{O}]
//[Ue] [\"{U}]
//[**] [$**$]
//[toc] [\tableofcontents]
//[=>] [\verb+=>+]
//[:Section Translation] [\label{sec:translation}]
//[Section Translation] [Section~\ref{sec:translation}]
//[:Section 4.1.1] [\label{sec:4.1.1}]
//[Section 4.1.1] [Section~\ref{sec:4.1.1}]
//[Figure pog1] [Figure~\ref{fig:pog1.eps}]
//[Figure pog2] [Figure~\ref{fig:pog2.eps}]
//[newpage] [\newpage]
[10] Example: Creating a Distributed Spatial Database
Ralf Hartmut G[ue]ting, February 2015
[toc]
[newpage]
1 Introduction
In this example, we set up a small distributed spatial database, using the DistributedAlgebra of Secondo. We will use a cluster of six computers each with six cores and two disks. On each disk we use one Secondo server resulting in 12 nodes (called ~workers~). A controlling Secondo master sits on another computer.
On all systems the platform is Linux, more specifically Ubuntu 14.04.
2 Passphrase-less Connection
The first thing is to set up an ssh connection from the master to the workers so that the system on the master can log in to the workers without the user having to enter a password. This is done as follows.
(1) In the master's home directory, enter
---- ssh-keygen -t dsa
----
This generates a public key. The system asks for a password, just type return for an empty password. Also type return for the default location to store the public key.
(2) Configure ssh to know the worker computers. Into a file .ssh/config, we put the following:
----
Host *node1
HostName 132.176.69.181
User gueting
Host *node2
HostName 132.176.69.182
User gueting
Host *node3
HostName 132.176.69.183
User gueting
Host *node4
HostName 132.176.69.184
User gueting
Host *node5
HostName 132.176.69.185
User gueting
Host *node6
HostName 132.176.69.186
User gueting
----
This makes the worker computers available with names node1, node2, etc. and also sets the user name and home directory on these computers.
(3) Transmit the public key to the target computers. In this case, the home directory on node1 is mapped to all other nodes, so it suffices to do this on node1.
----
ssh-copy-id -i .ssh/id_dsa.pub <user>@server
ssh-copy-id -i .ssh/id_rsa.pub gueting@132.176.69.181
----
If the master system does not have the command ssh-copy-id, one can use instead:
----
cat ~/.ssh/*.pub | ssh <user>@<server> 'umask 077; cat >>.ssh/authorized_keys'
----
At this point the system on node1 asks once for a password.
Subsequently it is possible to log in on node1 by simply typing
---- ssh node1
----
One should log in once on each of the nodes because the system on the master needs to enter each node into its list of known hosts.
3 Getting Spatial Data to the Master
We use OpenStreetMap data about the German state of North-Rhine-Westphalia obtained in the form of shapefiles from GeoFabrik.
On http://download.geofabrik.de/ one can navigate a bit selecting Europe, then Germany. Then from the table Sub Regions in the row for Nordrhein-Westfalen, download
http://download.geofabrik.de/europe/germany/nordrhein-westfalen-latest.shp.zip
You can also use this link to download directly, if it is still available. Unpack the zip file into a directory nrw within secondo/bin.
The database has 8 kinds of objects, for example, Roads, Buildings, Waterways.
In directory secondo/bin start a Secondo system without transactions (for loading data) using the command
---- SecondoTTYNT
----
At the prompt, enter
----
Secondo => @Scripts/nrwImportShape.SEC
----
The contents of the file nrwImportShape are shown here:
----
# Importing NRW Data
close database
create database nrw
open database nrw
let Roads = dbimport2('../bin/nrw/roads.dbf') addcounter[No, 1]
shpimport2('../bin/nrw/roads.shp') namedtransformstream[GeoData]
addcounter[No2, 1] mergejoin[No, No2] remove[No, No2]
filter[isdefined(bbox(.GeoData))] validateAttr consume
let Waterways = dbimport2('../bin/nrw/waterways.dbf') addcounter[No, 1]
shpimport2('../bin/nrw/waterways.shp') namedtransformstream[GeoData]
addcounter[No2, 1] mergejoin[No, No2] remove[No, No2]
filter[isdefined(bbox(.GeoData))] validateAttr consume
let Railways = dbimport2('../bin/nrw/railways.dbf') addcounter[No, 1]
shpimport2('../bin/nrw/railways.shp') namedtransformstream[GeoData]
addcounter[No2, 1] mergejoin[No, No2] remove[No, No2]
filter[isdefined(bbox(.GeoData))] validateAttr consume
let Points = dbimport2('../bin/nrw/points.dbf') addcounter[No, 1]
shpimport2('../bin/nrw/points.shp') namedtransformstream[GeoData]
addcounter[No2, 1] mergejoin[No, No2] remove[No, No2]
filter[isdefined(bbox(.GeoData))] validateAttr consume
let Places = dbimport2('../bin/nrw/places.dbf') addcounter[No, 1]
shpimport2('../bin/nrw/places.shp') namedtransformstream[GeoData]
addcounter[No2, 1] mergejoin[No, No2] remove[No, No2]
filter[isdefined(bbox(.GeoData))] validateAttr consume
let Natural = dbimport2('../bin/nrw/natural.dbf') addcounter[No, 1]
shpimport2('../bin/nrw/natural.shp') namedtransformstream[GeoData]
addcounter[No2, 1] mergejoin[No, No2] remove[No, No2]
filter[isdefined(bbox(.GeoData))] validateAttr consume
let Buildings = dbimport2('../bin/nrw/buildings.dbf') addcounter[No, 1]
shpimport2('../bin/nrw/buildings.shp') namedtransformstream[GeoData]
addcounter[No2, 1] mergejoin[No, No2] remove[No, No2]
filter[isdefined(bbox(.GeoData))] validateAttr consume
let Landuse = dbimport2('../bin/nrw/landuse.dbf') addcounter[No, 1]
shpimport2('../bin/nrw/landuse.shp') namedtransformstream[GeoData]
addcounter[No2, 1] mergejoin[No, No2] remove[No, No2]
filter[isdefined(bbox(.GeoData))] validateAttr consume
----
This creates relations Roads, Waterways, etc. within a new database nrw.
Recently, this database is fairly large, for example, has 5.8 mio buildings. It took more than 3 hours to run this script.
4 Selecting a More Convenient Smaller Area
For our example, we do not want long waiting times and restrict to the area of the city of Dortmund. By some method (for example, loading some of the data into the GUI of Secondo and either reading coordinates or creating a rectangle there), we define a rectangle
----
let Dortmund = [const rect value (7.344992561477086 7.7006749345239855
51.42492667118238 51.610366440983924)]
----
The rectangle coordinates in parentheses follow the format (x1 x2 y1 y2).
We create a directory Dortmund located in the home directory of the user. We then run a script with contents
----
let BuildingsDo = Buildings feed filter[bbox(.GeoData) intersects Dortmund]
consume
query BuildingsDo count
save BuildingsDo to '../../Dortmund/Buildings'
let LanduseDo = Landuse feed filter[bbox(.GeoData) intersects Dortmund]
consume
query LanduseDo count
save LanduseDo to '../../Dortmund/Landuse'
let NaturalDo = Natural feed filter[bbox(.GeoData) intersects Dortmund]
consume
query NaturalDo count
save NaturalDo to '../../Dortmund/Natural'
let PlacesDo = Places feed filter[bbox(.GeoData) intersects Dortmund]
consume
query PlacesDo count
save PlacesDo to '../../Dortmund/Places'
let PointsDo = Points feed filter[bbox(.GeoData) intersects Dortmund]
consume
query PointsDo count
save PointsDo to '../../Dortmund/Points'
let RailwaysDo = Railways feed filter[bbox(.GeoData) intersects Dortmund]
consume
query RailwaysDo count
save RailwaysDo to '../../Dortmund/Railways'
let RoadsDo = Roads feed filter[bbox(.GeoData) intersects Dortmund]
consume
query RoadsDo count
save RoadsDo to '../../Dortmund/Roads'
let WaterwaysDo = Waterways feed filter[bbox(.GeoData) intersects Dortmund]
consume
query WaterwaysDo count
save WaterwaysDo to '../../Dortmund/Waterways'
----
Now subsets of all relations intersecting the area of rectangle Dortmund have been selected and saved in the directory Dortmund.
We close the database nrw and create a new database dortmund. This is done by a script with the following contents:
----
close database
create database dortmund
open database dortmund
restore Buildings from '../../Dortmund/Buildings'
restore Landuse from '../../Dortmund/Landuse'
restore Natural from '../../Dortmund/Natural'
restore Places from '../../Dortmund/Places'
restore Points from '../../Dortmund/Points'
restore Railways from '../../Dortmund/Railways'
restore Roads from '../../Dortmund/Roads'
restore Waterways from '../../Dortmund/Waterways'
# Runtime for ../../Scripts/buildDortmund.sec: Times (elapsed / cpu): 5:39min
# (339.269sec) /194.8sec = 1.74163
----
Of course, you may apply the same techniques to select an area you are interested in anywhere on the earth (as far as OSM and GeoFabrik data are available).
5 Preparations for the Distributed Database
In the cluster, we need to set up configuration files for the Secondo instances to be used as servers on the 12 disks. We assume, Secondo is already installed on node1 in the home directory of user gueting. The configuration files are slight variations of the standard SecondoConfig.ini and are to be located in directory secondo/bin as well. We call them SecondoConfig.ini.SM1 and SecondoConfig.ini.SM2. The essential entries in these configuration files that need to be changed are the path to the secondo-database directory to be used and the port number.
Here we use:
----
SecondoConfig.ini.SM1:
=====================
# Note: On windows you need absolute path names, e.g.
# C:\msys\1.0\home\user\secondo-dbtest
SecondoHome=/opt/psec/gueting-databases
# Port of the Secondo server (default: )
SecondoPort=31234
SecondoConfig.ini.SM2:
=====================
# Note: On windows you need absolute path names, e.g.
# C:\msys\1.0\home\user\secondo-dbtest
SecondoHome=/mnt/diskb/psec2/gueting-databases
# Port of the Secondo server (default: )
SecondoPort=34321
----
The files must also be put into the secondo/bin directory of the master and must be the same in the secondo/bin directory of the master and in the secondo/bin directory of node1 !!
In the database dortmund, we now set up two relations describing the cluster.
To be used for startup:
----
let StartCluster = [const rel(tuple([Server: string, Port: int, SecConf:string]))
value (
("node1" 31234 "SecondoConfig.ini.SM1")
("node2" 31234 "SecondoConfig.ini.SM1")
("node3" 31234 "SecondoConfig.ini.SM1")
("node4" 31234 "SecondoConfig.ini.SM1")
("node5" 31234 "SecondoConfig.ini.SM1")
("node6" 31234 "SecondoConfig.ini.SM1")
("node1" 34321 "SecondoConfig.ini.SM2")
("node2" 34321 "SecondoConfig.ini.SM2")
("node3" 34321 "SecondoConfig.ini.SM2")
("node4" 34321 "SecondoConfig.ini.SM2")
("node5" 34321 "SecondoConfig.ini.SM2")
("node6" 34321 "SecondoConfig.ini.SM2")
)]
----
For use in queries, we set up the relation:
----
let Cluster = [const rel(tuple([Server: string, Port: int])) value (
("132.176.69.181" 31234)
("132.176.69.182" 31234)
("132.176.69.183" 31234)
("132.176.69.184" 31234)
("132.176.69.185" 31234)
("132.176.69.186" 31234)
("132.176.69.181" 34321)
("132.176.69.182" 34321)
("132.176.69.183" 34321)
("132.176.69.184" 34321)
("132.176.69.185" 34321)
("132.176.69.186" 34321)
)]
----
6 Starting the Cluster
From now on, within all Secondo systems the DistributedAlgebra must have been activated. It is also important to ensure that the Secondo system on the master and the cluster node Secondos have the same algebras activated. Otherwise it may happen that a query that runs on the master does not work on the servers.
We start all the servers listed in relation StartCluster by the query:
----
query StartCluster feed extend[Started: startup(.Server, .Port, .SecConf, FALSE)]
consume
----
The query uses the startup operator of the DistributedAlgebra. The last parameter says whether the database Distributed should be created/restored to empty on the cluster. So it should be set to TRUE when using the cluster the first time and FALSE when restarting with an existing Distributed database.
[At the moment, automatic construction of the database Distributed does not work. It has to be constructed manually on each server. The query can only be used with parameter FALSE, or omitting this parameter.]
We can check whether all workers are ok:
----
query Cluster feed check_workers consume
----
It should have a result:
----
Server : 132.176.69.181
Port : 31234
Status : OK
Server : 132.176.69.182
Port : 31234
Status : OK
Server : 132.176.69.183
Port : 31234
Status : OK
Server : 132.176.69.184
Port : 31234
Status : OK
Server : 132.176.69.185
Port : 31234
Status : OK
Server : 132.176.69.186
Port : 31234
Status : OK
Server : 132.176.69.181
Port : 34321
Status : OK
Server : 132.176.69.182
Port : 34321
Status : OK
Server : 132.176.69.183
Port : 34321
Status : OK
Server : 132.176.69.184
Port : 34321
Status : OK
Server : 132.176.69.185
Port : 34321
Status : OK
Server : 132.176.69.186
Port : 34321
Status : OK
----
If this result appears, the cluster is ready for use.
If in the Status field we have an entry ERROR: bnl, it means that the database Distributed does not exist.
Finally, we can shut down all servers by
----
query StartCluster feed extend[Stop: shutdown(.Server, .Port)] consume
----
As an abbreviation, we can define functions:
----
let ClusterOn = fun() StartCluster feed
extend[Started: startup(.Server, .Port, .SecConf, FALSE)] consume;
let ClusterCheck = fun() Cluster feed check_workers consume;
let ClusterOff = fun() StartCluster feed
extend[Stop: shutdown(.Server, .Port)] consume;
----
We can then for example start the cluster simply by typing:
----
query ClusterOn()
----
We now assume the cluster is active with the result shown above for check\_workers.
7 Creating the Distributed Database dortmund
7.1 Random distribution
The most simple way to distribute relations is a random distribution.
----
let Buildings12r = Buildings feed extend[N: randint(12)] ddistribute[N, 12, Cluster]
----
Time: 1:36 min
This creates a distributed array of relations called Buildings12r. The naming convention says that it is the relation ~Buildings~, distributed into 12 array slots (one per worker in this case) and it is a random distribution (signified by ~r~).
Distribution is relatively slow, taking 1:36 minutes.
We can check how the random function has distributed elements into array slots:
----
query Buildings12r dloop[. count]
----
The dloop operator does not really loop but applies in parallel its parameter query ``. count'' to each element of the array, which is a relation with the same schema as ~Buildings~. The element of the array is referred to by ``.''.
Result of the dloop operation is a distributed array with fields of the result type of the query, hence an array of int in this case. The result is displayed as follows.
----
+++++++++++++++ BEGIN DARRAY +++++++++++++++
+++++++++++++++ Workers: +++++++++++++++
("132.176.69.181" 31234)
("132.176.69.182" 31234)
("132.176.69.183" 31234)
("132.176.69.184" 31234)
("132.176.69.185" 31234)
("132.176.69.186" 31234)
("132.176.69.181" 34321)
("132.176.69.182" 34321)
("132.176.69.183" 34321)
("132.176.69.184" 34321)
("132.176.69.185" 34321)
("132.176.69.186" 34321)
++++++++++++ DArray Index: 0 +++++++++++++++
23877
++++++++++++ DArray Index: 1 +++++++++++++++
23480
++++++++++++ DArray Index: 2 +++++++++++++++
23480
++++++++++++ DArray Index: 3 +++++++++++++++
23874
++++++++++++ DArray Index: 4 +++++++++++++++
23909
++++++++++++ DArray Index: 5 +++++++++++++++
23655
++++++++++++ DArray Index: 6 +++++++++++++++
23779
++++++++++++ DArray Index: 7 +++++++++++++++
23502
++++++++++++ DArray Index: 8 +++++++++++++++
23734
++++++++++++ DArray Index: 9 +++++++++++++++
23729
++++++++++++ DArray Index: 10 +++++++++++++++
23678
++++++++++++ DArray Index: 11 +++++++++++++++
23714
+++++++++++++++ END DARRAY +++++++++++++++
----
The total number of elements of the original ~Buildings~ relation is obtained by:
----
query Buildings count
----
The result is 284411.
We can check whether the sum of the numbers of all array fields is the same:
----
query Buildings12r dloop[. count] dtie[. + ..]
----
Result: 284411
Time: 32.35 seconds
The dtie operator applies an aggregate function to all fields of the distributed array to which it is applied. The aggregate function is specified by showing how it combines two arguments. These are refereed to by ``.'' and ``..'', respectively.
The running time for this query is about 32 seconds. As the time for executing the count query on a relation is practically 0, one can observe that there is considerable overhead in establishing connections to workers (to be improved).
As an example of executing a query on the distributed relation, suppose we want to find all Buildings of type "school".
----
query Buildings12r dloop[. feed filter[.Type contains "school"] consume]
dsummarize consume
----
Time: 36.65 seconds
Here in the dloop operator, we construct a distributed relation containing all school buildings. The dsummarize operator reads a distributed relation from all workers into a stream of tuples on the master. This stream is then collected into a relation by consume and returned.
As an example of a spatial query (a range query) we want to find all buildings in the suburb "Eichlinghofen". In the GUI, we create a rectangle around this suburb (as seen on the background map) and name it "Eichlinghofen".
A query on the master alone can the be written as:
----
query Buildings feed filter[bbox(.GeoData) inside Eichlinghofen] consume
----
Time: 2.56 seconds
It returns 2198 buildings.
The parallel query is:
----
query Buildings12r dloop[. feed filter[bbox(.GeoData) inside
[const rect value (7.39815473431277 7.420728205382089
51.473026681164775 51.48697825360396)]]
consume] dsummarize consume
----
Time: 29.95 seconds
It returns exactly the same 2198 buildings. The running time corresponds to the overhead of the parallel query in connecting with servers.
Note that we need to replace the database object Eichlinghofen by a constant in the distributed query, because Eichlinghofen is only known in the master database. By
----
query Eichlinghofen
----
we can get the values of the constant. A future version of the DistributedAlgebra should automatically provide this replacement.
7.2 Distribution by Standard Attribute
A random distribution is not suitable for performing joins between distributed relations as it would be necessary to consider in the join all pairs of partitions (144 in our case) with the need of moving data between nodes.
If we want to support joins by some standard attribute (i.e., int, real, string) we may distribute based on hash values of the attributes.
For example, we distribute Roads by their Osm\_id key value.
----
let Roads12_Osm_id =
Roads feed extend[N: hashvalue(.Osm_id, 999997) mod 12] ddistribute[N]
----
Time: 24.94 sec
A join on the Roads via Osm\_id can now be executed in parallel on the partitions, without losing any join tuples. We first perform the join locally on the master:
----
query Roads feed {r1} Roads feed {r2} itHashJoin[Osm_id_r1, Osm_id_r2]
count
----
Result: 67892
----
query Roads count
----
Result: 67892
The join combines each tuple exactly with itself because Osm\_id is a key. The parallel version is:
----
query Roads12_Osm_id Roads12_Osm_id dloopa[. feed {r1} .. feed {r2}
itHashJoin[Osm_id_r1, Osm_id_r2] count] dtie[. + ..]
----
Time: 53.91 seconds, 51.38
Result: 67892
The dloopa operator performs a binary operation on two distributed arrays, using slots with the same index on each worker. Indeed, the result number of tuples is the same as in the non-distributed join. - Why this query takes such a long time, is not clear.
7.3 Distribution by Spatial Attribute
Distribution by a spatial attribute is more complex but will enable us to perform spatial joins within partitions without losing join pairs.
First we need to determine a grid covering all spatial objects in the database. We could define a grid within the rectangle of nrw, but here we show how to determine the bounding box of just the elements of the Dortmund database.
----
query
Buildings feed projectextend[; Box: bbox(.GeoData)]
Landuse feed projectextend[; Box: bbox(.GeoData)] concat
Natural feed projectextend[; Box: bbox(.GeoData)] concat
Places feed projectextend[; Box: bbox(.GeoData)] concat
Points feed projectextend[; Box: bbox(.GeoData)] concat
Railways feed projectextend[; Box: bbox(.GeoData)] concat
Roads feed projectextend[; Box: bbox(.GeoData)] concat
Waterways feed projectextend[; Box: bbox(.GeoData)] concat
aggregateB[Box; fun(r1: rect, r2: rect) r1 union r2; [const rect value undef]]
----
The result is: [const rect value (7.29786 7.75405 51.3778 51.6712)]
Next we create a 20 x 20 grid covering this area. It is no problem if the grid is a bit larger than the enclosing rectangle.
----
let grid = [const cellgrid2d value (7.29 51.37 0.025 0.025 20)]
----
Here the five parameters for the grid are the left bottom corner (7.29 51.37), the cellwidth and cellheight (each 0.25) and the number of cells in a row (20).
With an operator called cellnumber we can now assign to each spatial object in the area of the grid the numbers of the grid cells which are covered by its bounding box. The cellnumber operator takes a rectangle and a grid definition and returns a stream of numbers of the cells covered by the rectangle.
----
let Buildings12s = Buildings feed
extendstream[Cell: cellnumber(bbox(.GeoData), grid)]
extend[N: .Cell mod 12] ddistribute[N, 12, Cluster]
----
Time: 2:15 min
Each node is in charge of the objects intersecting the grid cells that are mapped to its number between 0 and 11.
The query
----
query Buildings12s dloop[. count] dtie[. + ..]
----
returns 287754 whereas the original Buildings relation has 284411 objects. The reason is that some objects fall on cell boundaries and so are mapped to more than one node.
We use the spatial distribution as the main organisation of the database and therefore also distribute the other classes of objects in the same way:
----
let Landuse12s = Landuse feed
extendstream[Cell: cellnumber(bbox(.GeoData), grid)]
extend[N: .Cell mod 12] ddistribute[N, 12, Cluster]
let Natural12s = Natural feed
extendstream[Cell: cellnumber(bbox(.GeoData), grid)]
extend[N: .Cell mod 12] ddistribute[N, 12, Cluster]
let Places12s = Places feed
extendstream[Cell: cellnumber(bbox(.GeoData), grid)]
extend[N: .Cell mod 12] ddistribute[N, 12, Cluster]
let Points12s = Points feed
extendstream[Cell: cellnumber(bbox(.GeoData), grid)]
extend[N: .Cell mod 12] ddistribute[N, 12, Cluster]
let Railways12s = Railways feed
extendstream[Cell: cellnumber(bbox(.GeoData), grid)]
extend[N: .Cell mod 12] ddistribute[N, 12, Cluster]
let Roads12s = Roads feed
extendstream[Cell: cellnumber(bbox(.GeoData), grid)]
extend[N: .Cell mod 12] ddistribute[N, 12, Cluster]
let Waterways12s = Waterways feed
extendstream[Cell: cellnumber(bbox(.GeoData), grid)]
extend[N: .Cell mod 12] ddistribute[N, 12, Cluster]
----
We can see the objects that have been assigned to a single server using the get operation.
----
query get(Buildings12s, 8);
query get(Waterways12s, 8)
----
The get operation returns just one field of a distributed array, in this case field 8, containing a relation.
% If we use these commands in the Javagui, we get a figure like the following:
% Figure 1: Buildings and Waterways assigned to Server no. 8. [Server8.eps]
The spatial distribution supports spatial join which can now be done within each partition. For example, suppose we want to find intersections between Roads and Waterways. The query on the master alone is:
----
query Roads feed {r} Waterways feed {w}
itSpatialJoin[GeoData_r, GeoData_w]
filter[.GeoData_r intersects .GeoData_w]
extend[C: crossings(.GeoData_r, .GeoData_w)] count
Time: 9.6 sec
Result: 1328
----
The parallel query looks like this:
----
query Roads12s Waterways12s dloopa[. feed {r} .. feed {w}
itSpatialJoin[GeoData_r, GeoData_w]
filter[.GeoData_r intersects .GeoData_w]
extend[C: crossings(.GeoData_r, .GeoData_w)] count]
dtie[. + ..]
Time: 1:09 min
Result: 1408
----
We can observe that the resulting number is a bit higher than on the master. The reason is that some objects are duplicated at cell boundaries and we also get duplicate results in the spatial join (because the same two objects meet in different cells). However, there is a technique to avoid these duplicates.
----
query Roads12s Waterways12s dloopa[. feed {r} .. feed {w}
itSpatialJoin[GeoData_r, GeoData_w]
filter[(.Cell_r = .Cell_w) and
gridintersects([const cellgrid2d value (7.29 51.37 0.025 0.025 20)],
bbox(.GeoData_r), bbox(.GeoData_w), .Cell_r)]
filter[.GeoData_r intersects .GeoData_w]
extend[C: crossings(.GeoData_r, .GeoData_w)] count]
dtie[. + ..]
Time: 1:04 min
Result: 1328
----
First, we want to compute the spatial join only within cells. There may be objects assigned to one server within different cells that overlap. We remove these pairs by the condition
---- (.Cell_r = .Cell_w)
----
Second, the same objects may meet within different cells even on different servers. The operator gridintersects takes a grid definition, two bounding boxes, and a cell number. It computes the smallest intersection point of the two boxes. This point can lie only within one cell. If this is the cell whose cell number is given, the predicate returns TRUE, otherwise FALSE. That means that only for one cell and only one server will the intersection of these two objects be considered. In this way duplicates across servers are avoided.
We can see that the result number from the join is indeed the same as on a single machine.
7.4 Shuffling
Sometimes it is necessary to repartition a given distributed relation because a subsequent join requires another partitioning. This can be done by the dshuffle operator.
For example, suppose we are given the distributed relation Buildings12r (randomly distributed) of Section 7.1 but want to perform the join via Osm\_id of Section 7.2.
----
query
Buildings12r dshuffle1[hashvalue(.Osm_id, 999997) mod 12]
Buildings12r dshuffle1[hashvalue(.Osm_id, 999997) mod 12]
dloopa[. feed {r1} .. feed {r2}
itHashJoin[Osm_id_r1, Osm_id_r2] count] dtie[. + ..]
----
Unfortunately, this query does not finish on the cluster because the DistributedAlgebra creates 8 Secondo instances on a given node wheras only 6 cores are available. (To be improved.)
----
let Temp2 = Buildings12r dshuffle1[hashvalue(.Osm_id, 999997) mod 12]
query Temp2 Temp2 dloopa[. feed {r1} .. feed {r2}
itHashJoin[Osm_id_r1, Osm_id_r2] count] dtie[. + ..]
----
Result: 284411
The result shows that the join has been executed correctly on the partitioning of Temp2. The first query uses 4 cores, the second 6.
The dshuffle1 operator redistributes to the same workers and number of slots as the input distributed array has. There are variants to use a different number of slots or even another set of workers.
*/