И снова здравствуй, Хабр! Сегодня поговорим об актуальной для многих из нас проблеме при работе с базами данных. В ходе работы над разными проектами часто приходится создавать базу данных (командное пространство, песочница и т.п.), которую использует как сам автор, так и/или коллеги для временного хранения данных. Как у любого «помещения», в нашей «песочнице» есть своё ограничение по объёму выделенного места для хранения данных. Периодически бывает так, что вы или ваши коллеги забываете об этом маленьком ограничении, из-за чего, к сожалению, заканчивается объём выделенной памяти.
В этом случае можно применить маленький лайфхак, который позволит оперативно просмотреть, какая таблица больше всего занимает место, кто её владелец, как долго она находится в общей песочнице и т.д. Используя его, вы оперативно сможете почистить место в песочнице, предварительно согласовав действия с владельцем данных без нанесения вреда данным остальных коллег. Кроме того, этот инструмент позволит периодически проводить мониторинг наполняемости вашей общей песочницы.
![](https://habrastorage.org/getpro/habr/upload_files/7b7/2f4/3ef/7b72f43ef741188eaa86e30da4a5b78e.png)
Этап 1
Для начала импортируем нужные нам модули библиотек для Python и определим путь к нашей песочнице.
import subprocess
import sys, getopt
import re
import pandas as pd
%matplotlib inline
pd.set_option('display.max_colwidth', -1)
path = 'hdfs://arnsdpsbx/user/team/team_sandbox/hive'
Модуль subprocess в Python предоставляет простые функции, которые позволяют нам запускать новый процесс и получать их коды возврата.
Модуль getopt — один из вариантов анализа аргументов командной строки. В основном используется для анализа последовательности аргументов.
Модуль re в Python — функции в этом модуле позволяют проверить, соответствует ли строка заданному регулярному выражению (или соответствует ли данное регулярное выражение определённой строке, что сводится к тому же самому).
Модуль pandas — программная библиотека для обработки и анализа данных. Команда %matplotlib inline позволяет построить и вывести нужный вам график прямо в Jupiter notebook (далее в статье).
Этап 2
Далее мы пропишем функции, которые позволят нам сформировать статистику по заполняемости нашей песочницы:
- для определения размера используемой памяти (human_readable_size)
def human_readable_size(size, decimal_places=2):
for unit in ['B', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB']:
if size < 1024.0 or unit == 'PiB':
break
size /= 1024.0
return f'{size:.{decimal_places}f} {unit}'
- для поиска и формирования списка пользователей в песочнице (get_ipa_login)
def get_ipa_login(domain, login):
if domain == 'Ваш_домен_IPA':
return f'{login}_Ваш_домен_IPA'
return ''
- для определения территориального подразделения (get_tb)
def get_tb(dept):
try:
return dept.split('/')[2]
except:
return '-'
- для определения наименования таблицы в песочнице (get_table_name)
def get_table_name(path):
try:
return path.split('/')[-1]
except:
return '-'
- для определения владельца таблицы (get_owner_name)
def get_owner_name(owner, name):
return f'{owner} ({name})'
- для определения таблиц «фантомов» без определения владельца
def get_fix_cmd(merge, path, table_name):
if merge == 'right_only':
return f'DROP TABLE {table_name}'
elif merge == 'left_only':
return f'hdfs dfs -rm -f -R -skipTrash {path}'
return ''
Этап 3
Далее мы создаем Dataframe, содержащий информацию о данных, размещённых в песочнице (данные о владельце, группе, дате, времени, пути размещения таблицы).
out = subprocess.Popen(['hdfs', 'dfs', '-du', path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, stderr = out.communicate()
if 'GSSException' in stdout.decode('utf-8'):
print('GSSException! Run kinit')
exit(1)
lines = stdout.decode('utf-8').split('\n')
list_du = []
for line in lines:
m = re.match(r'^(\d+)\s+(\d+)\s+(.*)$', line)
if m:
list_du.append([
int(m.group(1)),
int(m.group(2)),
m.group(3)
])
df_du = pd.DataFrame(list_du, columns=['fsize', 'disk_space_consumed', 'path'])
out = subprocess.Popen(['hdfs', 'dfs', '-ls', path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
stdout, stderr = out.communicate()
lines = stdout.decode('utf-8').split('\n')
del lines[0]
list_ls = []
for line in lines:
list_ls.append(line.split())
df_ls = pd.DataFrame(list_ls, columns=['permission', 'links', 'owner', 'group', 'size', 'date', 'time', 'path'])
df_ls.dropna(how='all', inplace=True)
df_ls.drop(['permission', 'links', 'size'], axis=1, inplace=True)
По написанному коду мы получаем данные о владельце, группе, дате, времени, пути размещения таблицы.
![](https://habrastorage.org/getpro/habr/upload_files/bc8/85c/3b0/bc885c3b0c3787dd30a23c8663e6cfaf.png)
Этап 4
Не забывая о коллегах, мы добавляем в наш Dataframe подробную информацию о пользователях, которым предоставлены права и которые используют нашу «песочницу». В нашем случае данные о пользователях и их принадлежности к территориальному подразделению мы взяли из сsv файла. В целом скрипт можно улучшить, настроив выгрузку на получение данных из системы учёта пользователей IPA (в нашем случае это Red HAT Identity Management).
df_users = pd.read_csv('ad_users.csv', encoding='windows-1251', sep='|', header=None, names=['domain', 'login', 'tn', 'full_name', 'position', 'dept'], index_col=False)
df_users['ipa_login'] = df_users.apply(lambda row: get_ipa_login(row.domain, row.login), axis=1)
Этап 5
После формирования баз с необходимой для нас информацией о таблицах и пользователях мы объединяем эти данные в единую базу данных.
df_hdfs = pd.merge(df_du, df_ls, on='path')
df_hdfs['fsize_h'] = df_hdfs['fsize'].apply(lambda x: human_readable_size(x))
df_hdfs['disk_space_consumed_h'] = df_hdfs['disk_space_consumed'].apply(lambda x: human_readable_size(x))
df_hdfs = df_hdfs.reindex(columns=['path', 'owner', 'group', 'fsize', 'fsize_h', 'disk_space_consumed', 'disk_space_consumed_h', 'date'])
df_hdfs = pd.merge(df_hdfs, df_users, how='left', left_on='owner', right_on='ipa_login')
df_hdfs['owner_name'] = df_hdfs.apply(lambda row: get_owner_name(row.owner, row.full_name), axis=1)
df_hdfs['tb'] = df_hdfs['dept'].apply(lambda x: get_tb(x))
df_hdfs['table_name'] = df_hdfs['path'].apply(lambda x: get_table_name(x))
Этап 6
Делаем ещё пару шагов для получения более точного наименования таблиц, размещённых в нашей «песочнице», собирая воедино и «отсекая» ненужную информацию.
out = subprocess.Popen([
'beeline',
'--outputformat=csv2',
'--silent=true',
'--showHeader=false',
'--verbose=false',
'--showWarnings=false',
'-u',
'"jdbc:hive2://<URL JUPITER HUB без https:// >:10000/default;principal=hive/_HOST@ВАШ домен"',
'-e',
'"show tables in Наименование Песочницы (башей базы);"'
],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
stdout,stderr = out.communicate()
lines = stdout.decode('utf-8').split('\n')
list_tables = []
for line in lines:
if 'warning:' not in line:
list_tables.append(line)
df_tables = pd.DataFrame(list_tables, columns=['table_name'])
df_tables.dropna(how='all', inplace=True)
В результате написанный код формирует наименование таблицы, установленное пользователем при её создании.
![](https://habrastorage.org/getpro/habr/upload_files/470/d66/a73/470d66a733e30a614c86c6db6d4a24d1.png)
После проведения подготовительных мероприятий по написанию кода и создания нужных нам данных в DataFrame перейдём к самому интересному — к просмотру результатов и анализу заполняемости нашей «песочницы».
Начнём с самого простого и выведем топ-10 таблиц по размерам использования памяти в «песочнице» с информацией о коллегах, которые их создали.
df_hdfs_top = df_hdfs.sort_values(by=['fsize'], ascending=False).head(10)[['path', 'fsize_h', 'owner_name']]
df_hdfs_top
![](https://habrastorage.org/getpro/habr/upload_files/615/ca4/30d/615ca430dbb8772e096185224e10be24.png)
Теперь давайте посмотрим на пользователей, которые больше всего занимают место в «песочнице», и выведем топ-10 из них.
df_hdfs_size_by_owner = df_hdfs.groupby('owner_name')['fsize'].sum().reset_index().sort_values(by=['fsize']).tail(10)
df_hdfs_size_by_owner.plot(x='owner_name', y='fsize', kind='barh')
![](https://habrastorage.org/getpro/habr/upload_files/a19/05c/34d/a1905c34db7126043fb3860663c5a450.png)
Одновременно посмотрим на заполняемость «песочницы» в разрезе территориальных подразделений.
df_hdfs_size_by_tb = df_hdfs.groupby('tb')['fsize'].sum().reset_index().sort_values(by=['fsize'])
df_hdfs_size_by_tb.plot(x='tb', y='fsize', kind='barh')
![](https://habrastorage.org/getpro/habr/upload_files/4dd/837/62f/4dd83762fc97376dd51a78dbeffbde31.png)
При необходимости посмотрим на наши таблицы по дате их создания, для уточнения у коллег их актуальности и планов по дальнейшему использованию.
df_hdfs_oldest = df_hdfs[df_hdfs.fsize > 10000].sort_values(by=['date'], ascending=False).tail(50) df_hdfs_oldest
![](https://habrastorage.org/getpro/habr/upload_files/5c8/f41/04c/5c8f4104c7c750c1a8d64997fec011d4.png)
Для более подробного просмотра результатов выгрузим данные в отдельный файл, который более подробно нам покажет информацию о таблицах, их объёме и владельцах.
df_hdfs_top = df_hdfs[df_hdfs.fsize > 10000].sort_values(by=['fsize'], ascending=False)
df_hdfs_top.to_csv('team_sandbox.csv', index=False)
Что ещё?
Также вы можете столкнуться с ситуацией, когда в вашей «песочнице» находятся HDFS файлы «фантомы» — таблицы без каталога и/или каталог без таблицы, а также файлы без принадлежности к определённому владельцу. Для их определения воспользуемся следующим кодом:
df_fantoms = pd.merge(df_hdfs, df_tables, how='outer', on='table_name', indicator=True).sort_values(by=['fsize'], ascending=False)
df_fantoms['fix_cmd'] = df_fantoms.apply(lambda row: get_fix_cmd(row._merge, row.path, row.table_name), axis=1)
df_fantoms = df_fantoms.reindex(columns=['path', 'table_name', 'owner_name', 'fsize', 'fsize_h', 'fix_cmd', '_merge'])
- для просмотра таблиц без каталога сформируем запрос
df_fantoms[df_fantoms._merge=='right_only']
- для просмотра каталогов без таблиц сформируем следующий запрос
print(human_readable_size(df_fantoms[df_fantoms._merge=='left_only']['fsize'].sum()))
df_fantoms[(df_fantoms._merge=='left_only') & (df_fantoms.fsize > 1000000000)]
- для более подробного просмотра и анализа «фантомов» выгрузим данные в отдельный файл
df_fantoms[(df_fantoms._merge=='left_only') & (df_fantoms.fsize > 1000000000)].to_csv('team_sandbox_fantoms.csv', index=False)
Данный подход является не единственным способом найти СЛОНА в «песочнице»: существует множество вариантов его реализации, от использования команд HDFS c её различными параметрами до написания Python кода под другим «углом» в зависимости от особенностей ведения вашей базы.