вторник, 23 сентября 2014 г.

Депрессия. Или превратности hResult и прочих ErrorCode

В общем - "в третий раз закинул старик невод"...

Точнее в третий раз не удался выпуск внутреннего продукта.

По моей вине.

Хотя и "со всеми пирогами", тестами, фабриками и прочим и прочим.

Тесты всякие разные - проходят.

Реальный софт в боевых условиях - не работает.

То READ_ERROR, то WRITE_ERROR. При распределённом доступе.

В общем - пока я с этим не разберусь и не переосмыслю, и не напишу - "рецепты лечения" - в блог писать - не буду.

Ибо - незачем.

Ибо - "теория слаба без практики".

Незачем писать, про "коней в вакууме", если у "тебя самого эти кони не работают".

Пока - сваяли "нагрузочный тест".

Оставил его "молотить" на ночь.

Завтра с утра - погляжу.

Может быть будем писать "другой нагрузочный тест".

Давненько таких epic fail'ов не случалось.

Одно только могу сказать "не про себя" - hResult и прочие "ErrorCode" скажем так - "не очень хорошая придумка". Ну как использовать конечно... Я не всегда правильно использовал...

Было:

SetFilePos(hFile, aPos);
FileWrite(hFile, @SomeValue, SizeOf(SomeValue));
SetFilePos(hFile, aPos);
FileRead(hFile, @SomeOtherValue, SizeOf(SomeOtherValue));
Assert(SomeValue = SomeOtherValue);

Стало:

SetFilePos(hFile, aPos);
FileWrite(hFile, @SomeValue, SizeOf(SomeValue));
SetFilePos(hFile, aPos);
SysCheck(FileRead(hFile, @SomeOtherValue, SizeOf(SomeOtherValue)));
// - тут стало "иногда" падать, хотя и SomeOtherValue = SomeValue
Assert(SomeValue = SomeOtherValue);

- что удивительно - без SysCheck - проверка проходила.

Т.е. ошибку таки возвращали, но ПРОВЕРКА проходила.

Т.е. вот так работает:

SetFilePos(hFile, aPos);
FileWrite(hFile, @SomeValue, SizeOf(SomeValue));
SetFilePos(hFile, aPos);
try
 SysCheck(FileRead(hFile, @SomeOtherValue, SizeOf(SomeOtherValue)));
 // - тут стало "иногда" падать, хотя и SomeOtherValue = SomeValue
finally
 Assert(SomeValue = SomeOtherValue);
 // - тут НЕ ПАДАЕМ, хотя и "падаем" выше, на SysCheck
end;

Т.е. "иногда читается то, что нужно", но "с ошибкой.

И БЕЗ проверки на ошибки - всё работало, но "подглючивало", с "проверкой на ошибки" - стало "чаще падать".

В условиях распределённой гетерогенной среды. С "дохлыми" и "полудохлыми" компьютерами.

И разными версиями Windows. Вплоть до "доисторических".

Почему? Пока - непонятно.

Грешу всё же на "собственные кривые руки", а не на "парней из Microsoft".

Ибо проще всего - "свалить на других парней".

Проще, но неконструктивно.

И кстати если написать:

SetFilePos(hFile, aPos);
FileWrite(hFile, @SomeValue, SizeOf(SomeValue));
l_TryCount := 0;
while (l_TryCount < 100) do
begin
 Inc(l_TryCount);
 SetFilePos(hFile, aPos);
 try
  SysCheck(FileRead(hFile, @SomeOtherValue, SizeOf(SomeOtherValue)));
  // - тут стало "иногда" падать, хотя и SomeOtherValue = SomeValue
 except
  if (l_TryCount < 100) then
   continue
  else
   raise;
 end;//try..except
 break;
end;
 Assert(SomeValue = SomeOtherValue);

То опять же - "падает гораздо реже".

Повод "для раздумий".

К сожалению - на "синтетических тестах" это - не повторяется.

Ну и "до кучи" - там ещё участвуют LockRegion/UnlockRegion.

Естественно - "правильно расставленные" и "правильным образом" обрамлённые SysCheck etc.

НО - похоже - всё дело всё же в их присутствии.

Без них - "типа работает", но с "конкурентным доступом" - плохо. Что и понятно.

Будем переходить на новый уровень тестирования.

P.S. весь приведённый код выше - это конечно же - "псевдокод". Лишь для иллюстрации проблем. "Запятые" там скорее всего - стоят неправильно. Ну и SysCheck при FileWrite - преднамеренно - опущен. Поверьте - он там есть.

И кроме того там ещё WrittenSize и ReadSize - проверяются.

Но эти детали - тоже специально - опущены.

P.P.S. Код без SysCheck и OleCheck уже лет 15-ть как работает, но "подглючивает" (время от времени, именно время от времени - получаются некорректные данные, "неприятно", но "жить типа можно"), собственно почему я и "полез разбираться" и писать SysCheck и OleCheck.

И "огрёб по полной программе".

Чего именно "огрёб"? Пока - не понял.

Ещё раз повторю - "далеко не на всех клиентских станциях" это повторяется. Возможно - "руки кривые".

В общем - "не забывайте про коды ошибок", но их "обработка" - тоже "не всегда однозначна".

Когда уличу себя в "кривых руках" - тогда обязательно напишу.

P.P.P.S. Ну и ещё я "вкрутил логирование" проблемных операций. И.. И.. Получил "кошку Шрёдингера". Логирование стало влиять на "бизнес-логику". Хотя бы "в части временн'ых задержек". Ну, что и понятно.

Тоже - "отдельная тема".

P.P.P.P.S. Да. И код выше приведён для одного клиента. А не то, что один пишет, а другой читает. Для разных клиентов - я всё понимаю. А для одного - пока не понимаю. Но скоро - надеюсь пойму.

P.P.P.P.S. И ещё.

Версию вида:

function DoRead(...): LongBool;
begin
 FileRead(hFile ...);
 // - тут "забыли" вернуть результат
end;

...

SysCheck(DoRead(...));
// - тут проверяем "мусор"

-- я уже проверил.

"Навскидку" - нету "неинициализированных переменных".

P.P.P.P.P.S. Тесты кстати пока "ещё молотят"... Без ошибок :-( Что "навевает грусть". Буду далее - утром смотреть.

P.P.P.P.P.S. Как выяснилось - чтобы были проблемы надо две вещи:

1. Доступ по UNC-путям, то есть по путям вида - \\server\resource\path\filename.
2. Обязательное использование LockFile.

P.P.P.P.P.P.S. Вчера тесты отработали без ошибок. Намолотили порядка 3 Гб данных.

Сегодня запустил тесты с двух машин. Завтра буду смотреть на результаты.

Потом буду запускать с 3-х, 4-х, 5-ти и т.д. и т.п.

P.P.P.P.P.P.P.S. И ещё сегодня я нашёл два "бутылочных горлышка" - AllocNewFATAlement и AllocNewCluster. Они в свою очередь ведут к LockFile, Лочим заголовок. Где записана информация о структуре хранилища. И все пользователи при записи "бъются от эти залочки".

И знаю уже как это разрулить.

Надо упреждающе аллоцировать не один FATElement и Cluster, а сразу несколько (пять, десять, двадцать). Одним куском. Учитывая, что кластеров в файле обычно не один и не два и даже не десять, то это - эффективно. И держать список свободных локально у клиента. А потом когда клиент закрывается - возвращать их в список свободных. Т.е. "не пригодившихся". Чтобы из потом могли использовать другие клиенты.

Есть конечно вероятность - потерять элементы, если клиент завершится аварийно.

Но зато - мы реже попадаем в "бутылочное горлышко".

Потому, что мы можем написать.

if AllocatedFatElements.Empty then
begin
 // - тут есть межПРОЦЕССНОЕ "бутылочное горлышко"
 Lock;
 try
  Result := AllocNewFatElement;
  for l_Index := 0 to 10 do
   AllocatedFatElements.Add(AllocNewFatElement);
 finally
  Unlock;
 end//try..finally
end
else
 // - тут ТОЛЬКО межПОТОЧНОЕ "бутылочное горлышко" (потому что AllocatedFatElements - естественно - многопоточно-защищён)
 Result := AllocatedFatElements.GetLastAndDeleteIt;

Вместо:

Lock;
// - а тут - ВСЕГДА - многоПРОЦЕССОРНОЕ "бутылочное горлышко"
try
 Result := AllocNewFatElement;
finally
 Unlock;
end;//try..finally

Ну и аналогично для кластеров.

А даже если и элементы "провиснут", то сильно хуже не будет, хранилище - не разрушится. Просто в нём будут "дырки".

Но учитывая тот факт, что у нас ночью происходит "ночной Update", если он возможен конечно, то хранилище - всё равно - перепаковывается. Т.е. "дырки" к "утру" - всё равно исчезнут.

И будет перепакованная постоянная часть без дырок. И "пустая" переменная часть.

Куда клиенты в течении дня пишут свои версии документов.

А следующей ночью - процесс опять повторяется.

Если конечно к базе не подключены работающие пользователи.

Отдельный пост - тут.

Оговорюсь - "это всё про внутренние продукты". Про внешние продукты - рассказывать не буду.

Ну и "для тех кто дочитал", вот так вот выглядит задача:


"Дефицит" :-(

27 комментариев:

  1. В Вашем псевдокоде мне показалось странным то, что с одной стороны, Вы говорите о конкурентном доступе, но с другой, в нём отсутствуют повторы операций ввода/вывода в случае ошибки ОС, возникающей при попытке доступа к блокированному другим процессом ресурсу (по-моему, это Access Denied). Блокировку Вы вероятно выполняете "выше по стеку".
    Повторы у Вас есть в последнем фрагменте (while (l_TryCount < 100)), но IMHO лучше между повторами сделать небольшую задержку, например 1/10 секунды.
    Впрочем, думаю Вы что-то подобное и так делаете - показан ведь только псевдокод...

    ОтветитьУдалить
    Ответы
    1. Не поверите :-) Всё так и есть. И Lock (выше по стеку), И sleep ;-)

      Приятно видеть умного человека.

      Sleep причём примерно такие - Sleep(Random(100)) - как мы говорим - "поспать немного".

      Почему Random? А чтобы "узорчик у разных клиентов отличался".

      Удалить
  2. "в нём отсутствуют повторы операций ввода/вывода в случае ошибки ОС"

    а можно "глупый вопрос"? Даже два.

    1. А "ошибки ОС" это норма? Их надо ВСЕГДА обрабатывать (это вопрос скорее риторический, но я с удовольствием вас послушаю).
    2. А почему Assert без SysCheck всё же работает? "Мусор" я конечно же "зачищаю" при помощи FillChar. Т.е. "типа не считалось", но SomeOtherValue - верное.

    ОтветитьУдалить
    Ответы
    1. А можно ещё вопрос?

      А вот в исходниках HyTech (которые вы переписывали) - вы встречали LockRegion? Или там "по-другому" с конкурентным доступом разбираются?

      Я сам туда - "лишь одним глазком" смотрел.

      И что там с "аналогами" SysCheck? Они присутствуют? И там while крутятся?

      Простите за такие вопросы.

      Я конечно могу и сам "на исходники посмотреть".

      Но вдруг вы сильно в курсе.

      Удалить
    2. «А вот в исходниках HyTech (которые вы переписывали) - вы встречали LockRegion? Или там "по-другому" с конкурентным доступом разбираются?»
      -- Насколько я могу судить - нет. Опосредованно используется LockFile из модуля Windows.

      Удалить
    3. http://msdn.microsoft.com/ru-ru/library/windows/desktop/aa365202(v=vs.85).aspx

      Ну я о нём и говорю. Просто "свои детали попутали".

      Удалить
    4. function TDBFile.FileLock( Ofs, Len : Longint ) : Longint ;
      begin
        if DWORD(fHandle) = INVALID_HANDLE_VALUE then
          begin
            Result := FileReOpen ;
            if Result < 0 then
              Exit ;
          end ;
        Result := 0 ;
        try
          while NeedIORetry(IO_LOCK,Result) do begin
            Result := sys.FileLock(fHandle,Ofs,Len);
            if Result = 0 then begin
              if Assigned(fIOState) then
                fIOState.FinishIOReq ;
              Inc(fLockCount) ;
              Exit ;
            end ;
          end ;
          SaveIOError(IO_LOCK,Result) ;
          Result := -HT_ERR_IO ;
        finally
          IOEventNotify( IO_LOCK, Ofs, Len ) ;
        end ;
      end;

      sys.FileLock - это обёртка над Windows.LockFile.

      Удалить
  3. «Sleep причём примерно такие - Sleep(Random(100)) - как мы говорим - "поспать немного".
    Почему Random? А чтобы "узорчик у разных клиентов отличался".»

    -- IMHO может негативно сказаться на стабильности воспроизведения проблемы.
    Кроме того, у Random есть вероятность "выпадения" слишком малых значений 1-10, а такая задержка - всё равно что её нет, да и сеть нагрузит больше...

    «1. А "ошибки ОС" это норма? Их надо ВСЕГДА обрабатывать»
    -- Я немного не понял вопрос. В частности, что Вы понимаете под "нормой".
    В случае, если всё работает правильно, разделяемый файл содержится в системе под управлением Windows, разные процессы используют один и тот же протокол по доступу к общему ресурсу и используют блокировки ОС на время обработки данных, ошибка ОС будет возникать в случае попытки одновременно разными процессами выполнить такую обработку.

    «2. А почему Assert без SysCheck всё же работает? "Мусор" я конечно же "зачищаю" при помощи FillChar. Т.е. "типа не считалось", но SomeOtherValue - верное.»
    -- Ну во-первых, мне не известно, что делает этот Ваш SysCheck. Предположу, что он верифицирует результат FileRead на равенство нулю (точнее там константа соответствующая есть).
    Во-вторых, почему нет? Мне неизвестно, как работает реализация FileRead на уровне ОС. Поскольку "в условиях отсутствия критерия истинности все домыслы равноправны" можно предположить, что чтение производится и в условиях, когда ресурс заблокирован, но помимо результата чтения возвращается признак того, что доступ запрещён (hResult соответствует access denied).

    ОтветитьУдалить
    Ответы
    1. "IMHO может негативно сказаться на стабильности воспроизведения проблемы."
      -- тут вы правы.

      Удалить
    2. "Ваш SysCheck"
      -- это не наш SysCheck, а стандартный.

      Удалить
    3. "можно предположить, что чтение производится и в условиях, когда ресурс заблокирован, но помимо результата чтения возвращается признак того, что доступ запрещён (hResult соответствует access denied)."

      -- вот это "похоже на правду", правда там не LOCK_VIOLATION. А что? Пока - не выяснил. Но выясню. "Не догадался" писать ErrorCode в лог. Теперь - пишу. Но версию - пока откатили.

      Удалить
    4. "ошибка ОС будет возникать в случае попытки одновременно разными процессами выполнить такую обработку"

      -- Ну да - наверное так.

      Удалить
    5. «"Ваш SysCheck"
      -- это не наш SysCheck, а стандартный.»

      -- Стыдно признаться, я не нашёл упоминания SysCheck в стандартных модулях...
      Не наведёте?

      «"Не догадался" писать ErrorCode в лог. Теперь - пишу. Но версию - пока откатили.»
      -- Вы это и без меня знаете, но на всякий случай...
      Лучше не прямо в файл - операции ввода/вывода могут вызвать "интерференцию" с другими операциями такого класса, привести к нежелательным задержкам, что может привести к тому, что проблема "уйдёт".
      Можно писать в хорошо буферизованный поток или, на худой конец, во что-то вроде StringList, периодически выполняя Flush для него...

      Удалить
    6. "вот это "похоже на правду""

      хотя...

      Там же "выше" - SysCheck(LockRegion(hFile, aPos, SizeOf(SomeValue)).

      А "ниже" - SysCheck(UnlockRegion(hFile, aPos, SizeOf(SomeValue)).

      -- т.е. "регион эксклюзивно залочен".

      Удалить
    7. "Можно писать в хорошо буферизованный поток или, на худой конец, во что-то вроде StringList, периодически выполняя Flush для него..."

      -- Ну и это - тоже делаем.

      Удалить
    8. "«"Ваш SysCheck"
      -- это не наш SysCheck, а стандартный.»
      -- Стыдно признаться, я не нашёл упоминания SysCheck в стандартных модулях...
      Не наведёте?"

      Ну конечно... Опять "своя специфика"... Запутался... Немного....

      procedure RaiseLastOSError;
      var
      LastError: Integer;
      Error: EOSError;
      begin
      LastError := GetLastError;
      if LastError <> 0 then
      Error := EOSError.CreateResFmt(@SOSError, [LastError,
      SysErrorMessage(LastError)])
      else
      Error := EOSError.CreateRes(@SUnkOSError);
      Error.ErrorCode := LastError;
      raise Error;
      end;

      {$IFDEF MSWINDOWS}
      { RaiseLastWin32Error }

      procedure RaiseLastWin32Error;
      begin
      RaiseLastOSError;
      end;

      { Win32Check }

      function Win32Check(RetVal: BOOL): BOOL;
      begin
      if not RetVal then RaiseLastOSError;
      Result := RetVal;
      end;

      Удалить
  4. Понял, что меня в Вашем псевдокоде смущает, принимая во внимание, что Вы сказали выше, что используете задержки и повторы. У нас так:

    procedure TDBFile.DoFileWrite(const Buf; Len: Cardinal);
    var
      Result: Integer;
    begin
      Result := 0;
      while NeedIORetry(IO_WRIT, Result) do
        begin
          Result := sysutils.FileSeek(fHandle, fPosition, soFromBeginning);
          if Result >= 0 then
            begin
              Result := sysutils.FileWrite(fHandle, Buf, Len);
              if Result = Len then
                begin
                  if Assigned(fIOState) then
                    fIOState.FinishIOReq;
                  Inc(fPosition, Result);
                  Exit;
                end;
            end;
          Result := GetLastError;
        end;
      SaveIOError(IO_WRIT, Result);
    end;

    procedure TDBFile.FileWrite(const Buf; Len: Cardinal);
    var
      Result: Integer;
      Pos: Longint;
      IOAction: TIOAction;
      IOFileWrite: TIOFileWrite;
    begin
      Result := 0;
      if DWORD(fHandle) = INVALID_HANDLE_VALUE then
        begin
          Result := FileReOpen;
          if Result < 0 then
            Exit;
        end;
      Pos := fPosition;
      try
        if Assigned(fIOSynchronize) then
          begin
            tMethod(IOAction).Code := @CallDoFileWrite;
            tMethod(IOAction).Data := @IOFileWrite;
            IOFileWrite.Buf := @Buf;
            IOFileWrite.Len := Len;
            IOFileWrite.F := Self;
            Result := fIOSynchronize(Self, IOAction);
            if Result < 0 then
              IOError := Result;
          end
        else
          DoFileWrite(Buf, Len);
      finally
        IOEventNotify(IO_WRIT, Pos, Len);
      end;
    end;

    т.е., если не ошибаюсь, повтору подвергается атомарная операция записи.
    Иными словами, в случае ошибки атомарная операция повторяется, в худшем случае предопределённое количество раз (на самом деле там баллистический алгоритм расчёта задержки), прежде чем будет возбуждено исключение.

    ОтветитьУдалить
    Ответы
    1. На всякий случай, что я имею ввиду под атомарностью.

      Исходный посыл.
      Я бы не доверял стандартным операциям ввода/вывода в том смысле, что исходил бы из предположения, что особенно при конкурентном доступе возможны различные ошибки, связанные с ним (конкурентным доступом), сетью (которая не надёжна по определению), ОС (которая живёт по "своим законам") и прочими вещами, о которых можно только догадываться.

      Исходя из этого, в случае, если атомарная операция ввода/вывода (FileRead, FileWrite, да и не только) вернула ошибку, я бы не особенно разбирался бы, чем она вызвана, а попытался бы через время (например, 1/10 секунды) повторить операцию начиная с позиционирования на интересующий фрагмент файла. И повторял бы до тех пор, пока либо не получится выполнить требуемое, либо, не исчерпается лимит повторов.

      Удалить
    2. Именно так и сделал :-) В итоге - вроде "подрассосалось"...

      Но вопрос даже не в этом, а в том как "это гарантированно тестировать".

      Удалить
    3. "это гарантированно тестировать"

      Ибо ни на Unit-тестах, ни на интеграционных, ни на нагрузочных. Ни на прочей "синтетике" - НЕ ПОВТОРЯЕТСЯ.

      А у "реальных пользователей" - повторяется.

      Только - "логирование" и "разбор логов" и "лечение по фотографии", но это - не "тестирование". И тем более - "не гарантированное".

      А вот - "гарантированное тестирование" - БОЛЬШОЙ ВОПРОС.

      Я его пока - не решил.

      С внешним софтом как ни странно - оказалось - "проще", нежели чем с внутренним.

      Удалить
  5. «Именно так и сделал :-) В итоге - вроде "подрассосалось"...»
    -- Слава Богу! :-)
    Интересно вот что: если "рассосалось", это значит повторы срабатывают.
    Но "научная добросовестность" требует прояснения номенклатуры ошибок ОС, которые вызывают необходимость этих повторов.
    Насколько я помню, там было минимум три разных ошибки, причём они были разными, в зависимости от того, какая ОС была установлена на файл-сервере (тогда мы использовали Novell и Windows NT 4.0).
    Неопределённость в этом вопросе и вызвала решение их игнорировать, производя попытки повторить требуемое действие до исчерпания лимита повторов.

    ОтветитьУдалить
    Ответы
    1. ""научная добросовестность" требует прояснения номенклатуры ошибок ОС, которые вызывают необходимость этих повторов."

      -- как только будет "полная картина" - я напишу.

      Удалить
  6. «Но вопрос даже не в этом, а в том как "это гарантированно тестировать".»
    -- Хороший вопрос... :-)
    Я как-то говорил Вам об этом раньше, выражая скепсис относительно повсеместного применения блочных тестов.
    Ведь главный вопрос здесь в том, что именно мы собираемся тестировать. Блоные тесты (и регрессионные в том числе) проверяют *правильность* работы приложения в заведомо известных условиях, т.е. тестированием по его определению не являются, но создают опасную иллюзию, что "у нас всё под контролем". - Нет, это конечно же не так.
    Но оставлю философию :-)
    Конструктивно, мой ответ: Никак. Мы должны писать код в предположении, что он написан неправильно, что он содержит ошибки, и что единственное, что мы можем сделать на уровне этого кода, это максимально детализировать происходящее, когда «что-то пойдёт не так».
    Разумеется, формальное применение этого принципа превратит жизнь в кошмар, поскольку в каждом конкретном месте обеспечивать полную детализацию слишком дорого.
    Поэтому в большинстве случаев я предпочитаю следовать простому принципу: исключение должно содержать однозначное указание на происходящее. Т.е. если есть несоответствие в управляющих переменных — вывести их значения. Если известен код ошибки ОС — он безусловно должен быть упомянут (возможно, это следует сделать автоматически, на уровне самого протоколирования, поскольку GetLastError – глобальная функция, доступная отовсюду). Кроме того, возбуждение исключения должно приводить к протоколированию стека — это уже хорошо.
    Разумеется, этим нельзя ограничиваться в некоторых случаях.
    Например, в Вашем.
    То, что Вы делаете - как я понимаю, разрабатываете собственную файловую систему внутри одного файла, это то, что я определяю как код повышенной ответственности.
    Такой код в моём понимании характеризуется сложностью определения наперёд контекста его исполнения. В таких случаях предварительное выявление критичных мест, т. е. мест, которые будут работать с повышенной нагрузкой (возможно, именно это Вы называете «бутылочным горлышком») необходимо. В таких местах требуется повышенная детализация, о которой я говорил выше. Крайне вероятно, что помимо детализации необходима ещё и статистика, посредством анализа которой в дальнейшем будут выявляться проблемы с производительностью (такой анализ привёл к появлению упомянутого выше баллистического алгоритма, рассчитывающего оптимальную задержку при повторах, что позволило адаптироваться к параметрам локальной сети).
    Ну и для выявленных критичных мест нужно написать провоцирующие тесты, для того, чтобы проверить их поведение в худших случаях.
    Но вообще-то, лучший способ «борьбы» с кодом повышенной ответственности — это не писать его вовсе. В конце концов, тысячи людей по всему миру пишут и отлаживают такой код за деньги, создавая бесплатные решения. IMHO грех и неуважение этим не воспользоваться.
    В своё время я «зарубил» идею разработки своего хранилища, аналогичного разрабатываемому Вами, как только стало очевидно, что без кластеризации обойтись не получится. Стало понятно, что это сложно, опасно для конечного продукта и, даже для деловой репутации, поскольку продукт предназначался на наших клиентов. Было найдено альтернативное, более простое решение, снявшее проблему хранения BLOB-ов в HyTech. Оно Вам не подойдёт, поскольку рассчитано на другие условия эксплуатации — параметры, обозначенные Вами в личке на это однозначно указывают, хотя для Oracle это конечно же — ерунда. Для PostgreSQL, Firebird и тем более, для NoSQL-решений, думаю (проверить быстро не могу) тоже, если я не ошибаюсь в оценках их размеров (приблизительно, до 100 MB, хотя тут важны параметры локальной сети — их ведь по ней придётся «тащить»). С другой стороны, если необходим прямой доступ к огромным бинарным данным — эти решения не годятся, тогда файловая система.

    ОтветитьУдалить
    Ответы
    1. "С другой стороны, если необходим прямой доступ к огромным бинарным данным — эти решения не годятся, тогда файловая система."

      -- файловая система - тоже "не всегда годится", хотя со счетов и не сбрасывается.

      Удалить
    2. "Но вообще-то, лучший способ «борьбы» с кодом повышенной ответственности — это не писать его вовсе. "

      - на "генетическом уровне" - я с вами согласен, но "опыт" к сожалению - диктует обратное.

      Удалить
  7. Рабочий код, порой, напоминает картины Сальвадора Дали:
    http://qq.by/uploads/posts/1356972619_45157872.jpg

    Идеально задуманные фигуры на куче костылей, а рядом зеленый программист и лохматый тестировщик хоронят самоубившегося архитектора :)

    ОтветитьУдалить
    Ответы
    1. Просто архитектор не должен "отрываться от корней".

      Удалить