Вопросы к гуру по BLOB полям.

Запросы, планы, оптимизация запросов, ...

Модераторы: kdv, CyberMax

Ответить
Vit
Сообщения: 19
Зарегистрирован: 05 ноя 2005, 03:22

Вопросы к гуру по BLOB полям.

Сообщение Vit » 16 фев 2008, 16:25

Добрый день.

Имеется система многоканальной записи речи. В настоящий момент в базу складывается вся служебная информация, а сами аудиоданные хранятся в отдельных файлах. Есть желание писать их в виде BLOB полей (Соображения безопасности, удобства удаленного доступа и администрирования). Потенциально сеанс может занимать и 20MB.

Интересует мнение уважаемых форумчан по перечисленным вопросам.

1. Как ведут себя сервера FB1.5 и FB2.0 c большими BLOB полями? Есть ли у кого-нибудь опыт эксплуатации таких систем? Интересует именно вопрос полей BLOB большого размера, а не большого количества записей.

Как поведет себя сервер если ему одновременно будут «наливаться», скажем, 32 BLOBа?

2. Какие будут соображения по способу «наливания» таких BLOB полей?
Имеется в виду следующее:

Цитата из FAQ ( http://www.ibase.ru/ibfaq.htm#blob )
....делать это можно двумя вариантами:
1. передача blob через параметр. IBQuery1.ParamByName('blb').asBlob:=blobvar;
2. 2. изменение столбца "редактируемого" DataSet - запись blob из файла в столбец:
IBDataSet1.Edit;
(IBDataSet1.FieldByName('BLB') as TBlobField).LoadFromFile('c:\blob.bin');
IBDataSet1.Post;
Очевидно, что во всех этих случаях требуется чтобы значение будущего поля BLOB находилось в памяти, что неприемлемо в случае больших полей. Работать через TIBBlobStream напрямую также не получается. В нем есть метод Write, однако анализ исходного текста показал, что он также собирает все в памяти, а физически записывает в методе TIBBlobStream.Finalize.

function TIBBlobStream.Write(const Buffer; Count: Longint): Longint;
begin
CheckWritable;
EnsureBlobInitialized;
result := Count;
if Count <= 0 then exit;
if (FPosition + Count > FBlobSize) then SetSize(FPosition + Count);
Move(Buffer, FBuffer[FPosition], Count); Inc(FPosition, Count);
FModified := True;
end;

Исходя из этого прихожу к выводу о необходимости лезть в API и самому вызывать

isc_create_blob2
isc_put_segment; // n-раз
isc_close_blob


3. Что произойдет в следующем случае:
при помощи функций API
начать транзакцию
начать заливать данные в поле BLOB
откатить транзакцию до вызова isc_close_blob
откатить транзакцию после вызова isc_close_blob, но не приклеив BLOB ID ни к какой записи.

4.
Цитата из FAQ
Сервер сохраняет blob следующим образом:
1. Если размер данных, записываемых в blob, помещается на свободном месте рядом с оригинальной записью, которой принадлежит blob - blob записывается на это место (при этом возникает "фрагментированность" страниц данных блобами, что в некоторых случаях может ухудшить производительность при обработке только записей). Объем свободного места зависит от размера страницы и количества записей, уже размещенных на этой странице.
2. Если свободного места на странице записей нет, то для blob выделяется отдельная страница или несколько, в зависимости от размера blob. Если после записи blob на такой странице осталось свободное место, то оно остается пустым и не будет занято другими blob.
Все красиво и понятно опять же в случае если заливать весь блоб целиком, а вот как быть если заливать его частями. Не произойдет ли следующее:Первый фрагмент сдуру поместился на странице с данными и соответственно похерит производительность, а потом все равно серверу придется выделять дополнительные страницы, но первый фрагмент сервер же переносить не будет?

kdv
Forum Admin
Сообщения: 6595
Зарегистрирован: 25 окт 2004, 18:07

Сообщение kdv » 16 фев 2008, 17:02

1. нормально, люди хранят такие блобы, только вопрос в их количестве. Мое мнение - когда блобы на порядки превышают объем самих данных, то хранить такие "блобы" в базе скорее всего невыгодно. То есть, хранение таких файлов в БД должно быть осмысленным и оправданным.
Например, если предполагать модификацию таких блобов в БД, то надо понимать, что при модификации в БД будет 2 копии блоба - старая и новая.

как поведет сервер - а ты попробуй. в любом случае заливка блоба будет медленнее, чем запись в файл.

2. напиши самостоятельно заливку блоба кусками.

3. что-то я не очень понял.

4. блоб или целиком помещается на странице данных, или целиком вне страниц данных.

hvlad
Разработчик Firebird
Сообщения: 1244
Зарегистрирован: 21 мар 2005, 10:48

Сообщение hvlad » 16 фев 2008, 17:58

1. Тестируй. Рекомендую 2.0 или 2.1

2. Да

3. Блобы будут удалены. На 2.0 - точно, за 1.5 - не скажу

4. Не произойдёт

Vit
Сообщения: 19
Зарегистрирован: 05 ноя 2005, 03:22

Сообщение Vit » 16 фев 2008, 18:36

kdv писал(а):1. нормально, люди хранят такие блобы, только вопрос в их количестве. Мое мнение - когда блобы на порядки превышают объем самих данных, то хранить такие "блобы" в базе скорее всего невыгодно. То есть, хранение таких файлов в БД должно быть осмысленным и оправданным.
Например, если предполагать модификацию таких блобов в БД, то надо понимать, что при модификации в БД будет 2 копии блоба - старая и новая.
Ваше мнение об осмысленности и оправданности хранения в следующем случае:
1. Модификация юзером не предполагается, более того должна быть максимально затруднена для продвинутого специалиста (система типа "Черный-ящик").
2. Требуется раз в день вытягивать всю накопившуюся за день инфу на клиента с 20 серверов. Если хранить в отдельных файлах, то требуется либо FTP-сервер, с паролями и администрированием онных, либо средствами Windows открывать доступ к папке с этими файлами, опять же администрирование паролей, необходимость с клиента ввода двух паролей (один от FB один от FTP или от Windows)....
kdv писал(а): 3. что-то я не очень понял.
В системе предполагается что запись может быть принудительно прервана без сохранения в базе. Имеется в виду следующее:
Поднята трубка - включилась запись
Произошел набор опрделенного номера - запись выключилась и записанный кусок удалился.
Сейчас создается файл. Если надо прервать запись, то файл закрывается и удаляется. А вот если заливать аудио как блоб, то могут возникнуть описанные ситуации. Что в этом случае произойдет? Страницы занятые этим блобом окажутся свободными и будут использованы сервером в дальнейшем?
kdv писал(а): 4. блоб или целиком помещается на странице данных, или целиком вне страниц данных.
hvlad писал(а): 4. Не произойдёт
А откуда сервер узнает полный размер блоба в случае его заливки по-кускам? Или он будет как IBX выделять кусок памяти, чтоб туда помещался блоб целиком, а реально записывать только по вызову isc_close_blob? Тогда это очень не хорошо для блобов по 20MB, особенно когда заливается 32 сразу.

hvlad
Разработчик Firebird
Сообщения: 1244
Зарегистрирован: 21 мар 2005, 10:48

Сообщение hvlad » 16 фев 2008, 22:10

Vit писал(а):В системе предполагается что запись может быть принудительно прервана без сохранения в базе. Имеется в виду следующее:
Поднята трубка - включилась запись
Произошел набор опрделенного номера - запись выключилась и записанный кусок удалился.
Сейчас создается файл. Если надо прервать запись, то файл закрывается и удаляется. А вот если заливать аудио как блоб, то могут возникнуть описанные ситуации. Что в этом случае произойдет? Страницы занятые этим блобом окажутся свободными и будут использованы сервером в дальнейшем?
В 2.0 и выше - да.
В 1.5 - скорее всего, да, точнее не помню
Vit писал(а):А откуда сервер узнает полный размер блоба в случае его заливки по-кускам? Или он будет как IBX выделять кусок памяти, чтоб туда помещался блоб целиком, а реально записывать только по вызову isc_close_blob? Тогда это очень не хорошо для блобов по 20MB, особенно когда заливается 32 сразу.
Сервер выделяет в памяти 1 страницу под данные блоба (не в страничном кеше!). Если она переполняется до закрытия блоба, то он однозначно хранится не на странице данных. Иначе - зависит от наличия свободного места на той странице данных, на которой нахидится запись с blob_id.

Vit
Сообщения: 19
Зарегистрирован: 05 ноя 2005, 03:22

Сообщение Vit » 18 фев 2008, 01:33

Спасибо большое hvlad.

hvlad
Разработчик Firebird
Сообщения: 1244
Зарегистрирован: 21 мар 2005, 10:48

Сообщение hvlad » 18 фев 2008, 10:31

Vit писал(а):Спасибо большое hvlad.
Спасибо не отделаешься :) Расскажешь о системе и поведении птицы под нагрузкой, как будет время\готовность ?

Vit
Сообщения: 19
Зарегистрирован: 05 ноя 2005, 03:22

Сообщение Vit » 20 мар 2008, 17:50

hvlad писал(а):
Vit писал(а):Спасибо большое hvlad.
Спасибо не отделаешься :) Расскажешь о системе и поведении птицы под нагрузкой, как будет время\готовность ?
Скоро буду готов. Непременно отчитаюсь. Пока вроде работает.

Если позволите Возникло еще несколько вопросов.

В программе такая последовательность:

....
isc_create_blob2(...)

isc_put_segment(@status, @blob_handle, 1, Buf)
isc_put_segment(@status, @blob_handle, 2, Buf)
isc_put_segment(@status, @blob_handle, 3, Buf)
isc_put_segment(@status, @blob_handle, 4, Buf)

isc_close_blob
... цепляем блоб к записи
isc_commit_transaction
.....вытаскиваеи blob_id для этого блоба

isc_open_blob()

и через isc_blob_info получаем инфу о нашем блобе.
В частности

isc_info_blob_num_segments: 4
isc_info_blob_max_segment:4
isc_info_blob_total_length:10
isc_info_blob_type:0

Все правильно, но вот вопрос по поводу blob_type:
в документации сказано:
Type of Blob (0: segmented, or 1: stream)
Однако ни каких других упоминаний о stream блоб я не нашел.

Как их создавать? В isc_create_blob2 нет такого параметра.

Где храниться та информация которая возвращается по isc_blob_info?

Кстати если выкачивать такой blob, то он и вовращать будет в такой же последовательности.

Для моей задачи это пофигу - у меня внутреннее кеширование записываемых данных и записывается все по кусками по 16К (кроме последнего куска естественно), как и в IBX, но все эти вопросы возникли в связи с тем правильно ли заливать блоб кусками по 16К, или лучше по 16К минус место для размера сегмента и еще чего-то. Как он вообще хранит блоб на страницах db, если при выкачивании знает какими кусками он заливался?

hvlad
Разработчик Firebird
Сообщения: 1244
Зарегистрирован: 21 мар 2005, 10:48

Сообщение hvlad » 21 мар 2008, 11:25

Vit писал(а):Все правильно, но вот вопрос по поводу blob_type:
в документации сказано:
Type of Blob (0: segmented, or 1: stream)
Однако ни каких других упоминаний о stream блоб я не нашел.

Как их создавать? В isc_create_blob2 нет такого параметра.
BPB для этого предназначен
Vit писал(а):Где храниться та информация которая возвращается по isc_blob_info?
В самом блобе в БД
Vit писал(а):Кстати если выкачивать такой blob, то он и вовращать будет в такой же последовательности.

Для моей задачи это пофигу - у меня внутреннее кеширование записываемых данных и записывается все по кусками по 16К (кроме последнего куска естественно), как и в IBX, но все эти вопросы возникли в связи с тем правильно ли заливать блоб кусками по 16К, или лучше по 16К минус место для размера сегмента и еще чего-то. Как он вообще хранит блоб на страницах db, если при выкачивании знает какими кусками он заливался?
Так и хранит - размер сегмента (2 байта), потом сам сегмент.
Для поточных (stream) блобов этого, есс-но, нет.

Vit
Сообщения: 19
Зарегистрирован: 05 ноя 2005, 03:22

Сообщение Vit » 21 мар 2008, 17:15

hvlad огромное спасибо.
Получилось создать Streamed Blob так:

bpb[0]:= Char(isc_bpb_version1);
bpb[1]:= Char(isc_bpb_type);
bpb[2]:= Char(1);
bpb[3]:= Char(isc_bpb_type_stream);

isc_create_blob2(@status, @hDB, @hTr, @hblob, @blob_id, 4, @bpb)

Кстати в документации по IB этот вопрос не освещается никак. Там даже нет упоминания о параметре isc_bpb_type и константе isc_bpb_type_stream (хотя в хидере это есть).

В контексте означенного есть предложение разработчикам Firebird.

Почему бы вам не предусмотреть в конфигурационном файле параметр, регулирующий создания блоба (streamed/segmented) если в bpb не указан параметр isc_bpb_type или вообще не указан bpb (что встречается наиболее часто, в частности в IBX). Ведь, как правило, segmented blob мало кому нужен (разве что для переноса старых специфических приложений, написанных на API), а место с страницах БД будет экономиться. В частности если приложение написанно на IBX, то оно (приложение) вообще не заметит разницы.

Кстати IBX пытается записывать сегменты размером 16384 байта. Из написанного hvlad следует что для помещения такого блоба на страницу потребуется 16386 байт, т.е 2 байта будут перелазить на следующую страницу БД, что не очень-то эффективно.

hvlad
Разработчик Firebird
Сообщения: 1244
Зарегистрирован: 21 мар 2005, 10:48

Сообщение hvlad » 21 мар 2008, 18:07

Vit писал(а):Кстати в документации по IB этот вопрос не освещается никак. Там даже нет упоминания о параметре isc_bpb_type и константе isc_bpb_type_stream (хотя в хидере это есть).
Это не единственный её недостаток :wink:
Vit писал(а):В контексте означенного есть предложение разработчикам Firebird.

Почему бы вам не предусмотреть в конфигурационном файле параметр, регулирующий создания блоба (streamed/segmented) если в bpb не указан параметр isc_bpb_type или вообще не указан bpb (что встречается наиболее часто, в частности в IBX). Ведь, как правило, segmented blob мало кому нужен (разве что для переноса старых специфических приложений, написанных на API), а место с страницах БД будет экономиться. В частности если приложение написанно на IBX, то оно (приложение) вообще не заметит разницы.
Ты считал, сколько места сэкономится ? :) А старые приложения вполне могут поломаться на streamEd блобах.
gbak вон их толи бекапить, толи ресторть не умел не очень давно (в 1.5 вроде фиксили, не помню, проверь на своей версии).
Vit писал(а):Кстати IBX пытается записывать сегменты размером 16384 байта. Из написанного hvlad следует что для помещения такого блоба на страницу потребуется 16386 байт, т.е 2 байта будут перелазить на следующую страницу БД, что не очень-то эффективно.
Даже если у тебя страница 16К, то не забывай о её заголовке, так что рассчёты твои неверны :)
Вообще макс. р-р сегмента равен 65535-2 байта, но я не могу быть уверен, что нигде не затесались знаковые переменные для работы с длиной сегмента,
так что могу рекомендовать использовать сегменты по 32765.
Или, конечно, streamed blob.

Vit
Сообщения: 19
Зарегистрирован: 05 ноя 2005, 03:22

Сообщение Vit » 07 апр 2008, 15:50

hvlad писал(а):
Vit писал(а):Спасибо большое hvlad.
Спасибо не отделаешься :) Расскажешь о системе и поведении птицы под нагрузкой, как будет время\готовность ?
Нутес отчет: #-o #-o ](*,) ](*,) #-o #-o

Сервер FB 2.0.3.12981 Superserver Windows

Залил больше 2 гб в базу. Заливались блобы одновременно с четырех соединений. И иногда (такова логика приложения) заливалось из одного соединения по 3 блоба сразу. Потоки примерно по 128 -256 кbit/sec по каждому соединению.
Использовал Streamed blobs.
Особых проблем при заливке не заметил. Утечек памяти также. Тормозов на сервере не заметил

Обнаружил одну неприятную особенность, которую впрочем легко обойти:

У меня в одном из приложении имеется функция «создать архив базы». Учитывая, что 99% данных это как раз блобы, кроме того они уже сжаты серьезным алгоритмом работает эта функция так: В новую базу (или в выбранную) просто переносятся (копируются) выбранные данные из рабочей базы. Существует вероятность того, что user захочет скопировать еще раз запись, которая уже в архиве имеется (нарушение первичного ключа). Вот как выглядит (очень упрощенно) фрагмент кода:

Код: Выделить всё

IBSQLTarget.sql.text:= ‘insert into records (recid,wavedata) values (:recid,:wavedata)’;

isc_create_blob2(…@blob_id)
n  times isc_put_segment(…)
isc_close_blob(...)

IBSQLTarget.ParamByName('WAVEDATA').AsQuad:= blob_id;
IBSQLTarget.ParamByName('RECID').AsInteger:= rec_id; 
try
   IBSQLTarget.ExecQuery;
   IBTransactionTarget.CommitRetaining;
except
    on E:EIBInterBaseError do begin
          // bypass unique_key_violation exception
        if E.IBErrorCode <> isc_unique_key_violation then raise else
           IBTransactionTarget.RollbackRetaining;
    end;
 end;
В случае если происходит нарушение ограничения первичного ключа (поле RECID) созданный блоб не будет приклеен ни к одной записи в таблице. Я полагал, что в этом случае страницы занятые им будут использованы в дальнейшем, однако, по-моему, это не так. Во всяком случае я этого не заметил. Опыт ставился так: пытался скопировать в другую базу одни и теже данные два раза. В результате получил файл базы примерно в два раза больше чем надо. Запись с блобом в таблице не одна и не две, а около 2000.
hvlad писал(а):
Vit писал(а): Vit писал(а):
В системе предполагается что запись может быть принудительно прервана без сохранения в базе. Имеется в виду следующее:
Поднята трубка - включилась запись
Произошел набор опрделенного номера - запись выключилась и записанный кусок удалился.
Сейчас создается файл. Если надо прервать запись, то файл закрывается и удаляется. А вот если заливать аудио как блоб, то могут возникнуть описанные ситуации. Что в этом случае произойдет? Страницы занятые этим блобом окажутся свободными и будут использованы сервером в дальнейшем?
В 2.0 и выше - да.

Решил обходить следующим образом (мне так даже лучше):

Код: Выделить всё

IBSQLTarget.sql.text:= ‘insert into records (recid,wavedata) values (:recid,NULL)’;
IBSQLTarget2.sql.text:= ‘update records set wavedata = :wavedata where recid=:recid;

IBSQLTarget.ParamByName('RECID').AsInteger:= rec_id; 
IBSQLTarget2.ParamByName('RECID').AsInteger:= rec_id; 
IBSQLTarget2.ParamByName('WAVEDATA').AsQuad:= blob_id;

try
   IBSQLTarget.ExecQuery;  // insert может вызвать нарушение первичного ключа

   isc_create_blob2(…@blob_id)
   n  times isc_put_segment(…)
   isc_close_blob(...)

   IBSQLTarget2.ExecQuery; // update не может вызвать нарушение первичного ключа
   IBTransactionTarget.CommitRetaining;
except
    on E:EIBInterBaseError do begin
          // bypass unique_key_violation exception 
        if E.IBErrorCode <> isc_unique_key_violation then raise;
    end;
 end;
Имею один вопросик не относящийся непосредственно к блобам, однако очень желательный в описанном приложении, буду благодарен за подсказки где рыть:

Задача стоит узнать (примерно оценить), сколько еще данных можно записать в базу. Т.е. свободное место на диске, на которое может вырасти файл БД + суммарный размер свободных страниц в самом файле БД. Вот как бы узнать второе? Нужно для того, чтобы отдельный процесс, удалял наиболее старые записи, и делал возможность записи неограниченно долгой.

hvlad
Разработчик Firebird
Сообщения: 1244
Зарегистрирован: 21 мар 2005, 10:48

Сообщение hvlad » 07 апр 2008, 16:41

Vit писал(а):Обнаружил одну неприятную особенность, которую впрочем легко обойти:
...
В случае если происходит нарушение ограничения первичного ключа (поле RECID) созданный блоб не будет приклеен ни к одной записи в таблице. Я полагал, что в этом случае страницы занятые им будут использованы в дальнейшем, однако, по-моему, это не так.
Блобы привязаны к тр-ции. Временные блобы удаляются по commit\rollback, но из-за retaining они остаются жить...
Vit писал(а):Задача стоит узнать (примерно оценить), сколько еще данных можно записать в базу. Т.е. свободное место на диске, на которое может вырасти файл БД + суммарный размер свободных страниц в самом файле БД. Вот как бы узнать второе? Нужно для того, чтобы отдельный процесс, удалял наиболее старые записи, и делал возможность записи неограниченно долгой.
Вызова isc_database_info, который бы подсчитывал кол-во свободных страниц, нет (хотя вроде что-то такое есть в трекере).
Можно попробовать снять статистику и вычесть занятые объектами страницы из общего р-ра БД, но это не учитывает служебные страницы, занятые под TIP (можно посчитать из Next) и т.п. Правда их относительно мало, так что может и подойдёт.

Ответить