decentriq_util 0.1.0.dev1__tar.gz → 0.1.0.dev3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: decentriq_util
3
- Version: 0.1.0.dev1
3
+ Version: 0.1.0.dev3
4
4
  Summary: Utility functions for Python code run in Decentriq workers
5
5
  Author: hafenr
6
6
  Author-email: robin.hafen@gmail.com
@@ -13,4 +13,5 @@ Classifier: Programming Language :: Python :: 3.12
13
13
  Requires-Dist: pandas (>=1.4.4)
14
14
  Requires-Dist: protobuf (>=4.21,<5.0)
15
15
  Requires-Dist: psutil (>=5.9.6,<6.0.0)
16
+ Requires-Dist: pyarrow (>=14.0.0,<15.0.0)
16
17
  Requires-Dist: pyspark (>=3.4.0,<4.0.0)
@@ -7,6 +7,7 @@ import os
7
7
  import shutil
8
8
  import psutil
9
9
 
10
+ import pyarrow.parquet as pq
10
11
  from pyspark.sql import SparkSession
11
12
 
12
13
 
@@ -33,7 +34,34 @@ def _get_cgroup_memory() -> int:
33
34
  return max_memory
34
35
  else:
35
36
  print("Unable to determine available memory from cgroup, assuming 4G")
36
- return 4 * 1024 * 1024
37
+ return 4 * 1024 * 1024 * 1024
38
+
39
+
40
+ def _get_uncompressed_parquet_size(path: str):
41
+ def is_parquet(filename: str):
42
+ return filename.endswith(".parquet")
43
+ def get_uncompressed_size(file_path: str):
44
+ parquet_file = pq.ParquetFile(file_path)
45
+ metadata = parquet_file.metadata
46
+ uncompressed_size = 0
47
+ for i in range(metadata.num_row_groups):
48
+ row_group = metadata.row_group(i)
49
+ for j in range(row_group.num_columns):
50
+ column = row_group.column(j)
51
+ uncompressed_size += column.total_uncompressed_size
52
+ return uncompressed_size
53
+ if os.path.isfile(path) and is_parquet(path):
54
+ uncompressed_size = get_uncompressed_size(path)
55
+ return uncompressed_size
56
+ # Path might be a subdir as obtained by `partitionBy`
57
+ elif os.path.isdir(path):
58
+ total_uncompressed_size = 0
59
+ for file in os.listdir(path):
60
+ file_path = os.path.join(path, file)
61
+ total_uncompressed_size += _get_uncompressed_parquet_size(file_path)
62
+ return total_uncompressed_size
63
+ else:
64
+ return 0
37
65
 
38
66
 
39
67
  def _get_num_partitions(
@@ -44,36 +72,43 @@ def _get_num_partitions(
44
72
  """Determine the number of partitions s.t. each partition is as big as possible
45
73
  while still making sure all cores are occupied."""
46
74
  def get_file_or_dir_size(path):
47
- if os.path.isfile(path):
48
- size = os.path.getsize(path)
49
- elif os.path.isdir(path):
50
- total_size = 0
51
- for dirpath, dirnames, filenames in os.walk(path):
52
- for f in filenames:
53
- fp = os.path.join(dirpath, f)
54
- total_size += os.path.getsize(fp)
55
- size = total_size
75
+ if path.endswith(".parquet"):
76
+ return _get_uncompressed_parquet_size(path)
56
77
  else:
57
- raise ValueError(f"The path {path} is neither a file nor a directory.")
78
+ if os.path.isfile(path):
79
+ size = os.path.getsize(path)
80
+ elif os.path.isdir(path):
81
+ total_size = 0
82
+ for dirpath, dirnames, filenames in os.walk(path):
83
+ for f in filenames:
84
+ fp = os.path.join(dirpath, f)
85
+ total_size += os.path.getsize(fp)
86
+ size = total_size
87
+ else:
88
+ raise ValueError(f"The path {path} is neither a file nor a directory.")
58
89
  return size
59
90
 
60
91
  file_size = max([get_file_or_dir_size(path) for path in paths])
92
+ if file_size == 0:
93
+ return 200 # default number of partitions in spark
61
94
 
62
95
  # Don't make the partitions smaller than 36MiB
63
96
  megabyte_in_bytes = 1024 * 1024
64
97
  min_partition_size = 32 * megabyte_in_bytes
65
98
 
66
99
  # Try to make partitions as big as possible s.t. each CPU is saturated,
67
- # while accounting for 20% overhead in spark.
68
- target_partition_size = max(
69
- int(0.8 * available_memory / cpu_count) // megabyte_in_bytes * megabyte_in_bytes,
100
+ # while accounting for 50% overhead in spark.
101
+ task_per_cpu = 3 # try to get at least this many tasks/partitions per CPU
102
+ max_target_partition_size = max(
103
+ int(0.5 * available_memory / (task_per_cpu * cpu_count)) // megabyte_in_bytes * megabyte_in_bytes,
70
104
  min_partition_size
71
105
  )
72
106
 
107
+ target_partition_size = max_target_partition_size
73
108
  while (
74
109
  # If the file is small enough that it cannot be split across all cores,
75
110
  # divide the partition size further.
76
- int(file_size / target_partition_size) < cpu_count
111
+ int(file_size / target_partition_size) < (task_per_cpu * cpu_count)
77
112
  ):
78
113
  # Don't go smaller than the minimum parition size
79
114
  if target_partition_size / 2 < min_partition_size:
@@ -81,6 +116,7 @@ def _get_num_partitions(
81
116
  target_partition_size = target_partition_size / 2
82
117
 
83
118
  print(f"File size: {file_size}")
119
+ print(f"Max target partition size: {max_target_partition_size}")
84
120
  print(f"Target partition size: {target_partition_size}")
85
121
 
86
122
  num_partitions = int(math.ceil(file_size / target_partition_size))
@@ -90,7 +126,7 @@ def _get_num_partitions(
90
126
 
91
127
  def _determine_optimal_spark_settings(
92
128
  files: list[str],
93
- heap_size_extra_room: Optional[int] = 512 * 1024 * 1024
129
+ heap_size_extra_room: Optional[int] = 2 * 512 * 1024 * 1024
94
130
  ) -> list[Tuple[str, str]]:
95
131
  def get_spark_memory() -> int:
96
132
  """Determine the amount of memory available to spark"""
@@ -100,16 +136,14 @@ def _determine_optimal_spark_settings(
100
136
 
101
137
  cpu_count = multiprocessing.cpu_count()
102
138
  spark_memory = get_spark_memory()
139
+ spark_memory_4096 = (spark_memory // 4096) * 4096
103
140
  num_partitions = _get_num_partitions(files, spark_memory, cpu_count)
104
141
 
105
- print(f"CPU count: {cpu_count}")
106
- print(f"Spark memory: {spark_memory}")
107
- print(f"Num partitions: {num_partitions}")
108
-
109
142
  settings = [
110
143
  ("spark.sql.shuffle.partitions", str(num_partitions)),
111
144
  ("spark.default.parallelism", str(num_partitions)),
112
145
  ("spark.driver.cores", str(cpu_count)),
146
+ ("spark.driver.memory", str(spark_memory_4096)),
113
147
  ]
114
148
  print("Spark settings:\n" + f"\n".join([str(x) for x in settings]))
115
149
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "decentriq_util"
3
- version = "0.1.0-dev1"
3
+ version = "0.1.0-dev3"
4
4
  description = "Utility functions for Python code run in Decentriq workers"
5
5
  authors = ["hafenr <robin.hafen@gmail.com>"]
6
6
 
@@ -10,6 +10,7 @@ protobuf = "^4.21"
10
10
  pandas = ">=1.4.4"
11
11
  pyspark = "^3.4.0"
12
12
  psutil = "^5.9.6"
13
+ pyarrow = "^14.0.0"
13
14
 
14
15
  [tool.poetry.dev-dependencies]
15
16
  pytest = "^7.2.0"